diff --git a/metadata/info.guardianproject.otr.app.im.txt b/metadata/info.guardianproject.otr.app.im.txt index f529f2d496..855bf941f9 100644 --- a/metadata/info.guardianproject.otr.app.im.txt +++ b/metadata/info.guardianproject.otr.app.im.txt @@ -25,12 +25,15 @@ sed -i 's@\(android.library.reference.1=\).*@\1$$ActionBarSherlock$$@' project.p Build Version:0.0.10-RC6,41,0.0.10-RC6,forceversion=yes,forcevercode=yes,\ srclibs=ActionBarSherlock@4.1.0,prebuild=\ sed -i 's@\(android.library.reference.1=\).*@\1$$ActionBarSherlock$$@' project.properties -Build Version:0.0.11-RC2,53,!bash script and private submodules 0.0.11-RC2,submodules=yes,\ -update=external/ActionBarSherlock/library;external/MemorizingTrustManager;external/OnionKit/library,\ -prebuild=sed -i '6 i \\t' \ -external/MemorizingTrustManager/AndroidManifest.xml && \ -echo "sdk-location=$$SDK$$" > external/asmack/local.properties && \ -cd external/asmack && /bin/sh build.bash && cp build/asmack-android-4.jar ../../libs +# Can't use build.bash script so add the output as a patch and build the jar manually +Build Version:0.0.11-RC5,57,!mysterious aapt problem at 111494ebe,\ +submodules=yes,patch=otr-asmack.patch,\ +update=.;external/ActionBarSherlock/library;external/\ +MemorizingTrustManager;external/OnionKit/library,prebuild=\ +rm -rf tests/ META-INF/ robo-tests/ gitian/ proguard-project.txt && \ +echo "sdk-location=$$SDK$$" > external/asmack/local.properties,build=\ +ant -Dbuild.all=true -f external/asmack/build.xml && \ +mv external/asmack/build/asmack-android-4.jar libs/ Auto Update Mode:None #Seem to bump manifest at beginning and tags doesn't give the latest diff --git a/metadata/info.guardianproject.otr.app.im/otr-asmack.patch b/metadata/info.guardianproject.otr.app.im/otr-asmack.patch new file mode 100644 index 0000000000..9f68d455d3 --- /dev/null +++ b/metadata/info.guardianproject.otr.app.im/otr-asmack.patch @@ -0,0 +1,127262 @@ +Instead of using the build.bash script which doesn't work with dash shell, apply the patch as a patch + +diff --git a/external/asmack/build/src/trunk/META-INF/services/com.kenai.jbosh.HTTPSender b/external/asmack/build/src/trunk/META-INF/services/com.kenai.jbosh.HTTPSender +new file mode 100644 +index 0000000..3608d8e +--- /dev/null ++++ b/external/asmack/build/src/trunk/META-INF/services/com.kenai.jbosh.HTTPSender +@@ -0,0 +1 @@ ++com.kenai.jbosh.ApacheHTTPSender +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AbstractAttr.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AbstractAttr.java +new file mode 100644 +index 0000000..0d6f84c +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AbstractAttr.java +@@ -0,0 +1,116 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * Abstract base class for creating BOSH attribute classes. Concrete ++ * implementations of this class will naturally inherit the underlying ++ * type's behavior for {@code equals()}, {@code hashCode()}, ++ * {@code toString()}, and {@code compareTo()}, allowing for the easy ++ * creation of objects which extend existing trivial types. This was done ++ * to comply with the prefactoring rule declaring, "when you are being ++ * abstract, be abstract all the way". ++ * ++ * @param type of the extension object ++ */ ++abstract class AbstractAttr ++ implements Comparable { ++ ++ /** ++ * Captured value. ++ */ ++ private final T value; ++ ++ /** ++ * Creates a new encapsulated object instance. ++ * ++ * @param aValue encapsulated getValue ++ */ ++ protected AbstractAttr(final T aValue) { ++ value = aValue; ++ } ++ ++ /** ++ * Gets the encapsulated data value. ++ * ++ * @return data value ++ */ ++ public final T getValue() { ++ return value; ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Object method overrides: ++ ++ /** ++ * {@inheritDoc} ++ * ++ * @param otherObj object to compare to ++ * @return true if the objects are equal, false otherwise ++ */ ++ @Override ++ public boolean equals(final Object otherObj) { ++ if (otherObj == null) { ++ return false; ++ } else if (otherObj instanceof AbstractAttr) { ++ AbstractAttr other = ++ (AbstractAttr) otherObj; ++ return value.equals(other.value); ++ } else { ++ return false; ++ } ++ } ++ ++ /** ++ * {@inheritDoc} ++ * ++ * @return hashCode of the encapsulated object ++ */ ++ @Override ++ public int hashCode() { ++ return value.hashCode(); ++ } ++ ++ /** ++ * {@inheritDoc} ++ * ++ * @return string representation of the encapsulated object ++ */ ++ @Override ++ public String toString() { ++ return value.toString(); ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Comparable interface: ++ ++ /** ++ * {@inheritDoc} ++ * ++ * @param otherObj object to compare to ++ * @return -1, 0, or 1 ++ */ ++ @SuppressWarnings("unchecked") ++ public int compareTo(final Object otherObj) { ++ if (otherObj == null) { ++ return 1; ++ } else { ++ return value.compareTo(otherObj); ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AbstractBody.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AbstractBody.java +new file mode 100644 +index 0000000..4d66c8c +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AbstractBody.java +@@ -0,0 +1,104 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.util.Collections; ++import java.util.Map; ++import java.util.Set; ++ ++/** ++ * Class representing a single message to or from the BOSH connection ++ * manager (CM). ++ *

++ * These messages consist of a single {@code body} element ++ * (qualified within the BOSH namespace: ++ * {@code http://jabber.org/protocol/httpbind}) and contain zero or more ++ * child elements (of any namespace). These child elements constitute the ++ * message payload. ++ *

++ * In addition to the message payload, the attributes of the wrapper ++ * {@code body} element may also need to be used as part of the communication ++ * protocol being implemented on top of BOSH, or to define additional ++ * namespaces used by the child "payload" elements. These attributes are ++ * exposed via accessors. ++ */ ++public abstract class AbstractBody { ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Constructor: ++ ++ /** ++ * Restrict subclasses to the local package. ++ */ ++ AbstractBody() { ++ // Empty ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Public methods: ++ ++ /** ++ * Get a set of all defined attribute names. ++ * ++ * @return set of qualified attribute names ++ */ ++ public final Set getAttributeNames() { ++ Map attrs = getAttributes(); ++ return Collections.unmodifiableSet(attrs.keySet()); ++ } ++ ++ /** ++ * Get the value of the specified attribute. ++ * ++ * @param attr name of the attribute to retriece ++ * @return attribute value, or {@code null} if not defined ++ */ ++ public final String getAttribute(final BodyQName attr) { ++ Map attrs = getAttributes(); ++ return attrs.get(attr); ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Abstract methods: ++ ++ /** ++ * Get a map of all defined attribute names with their corresponding values. ++ * ++ * @return map of qualified attributes ++ */ ++ public abstract Map getAttributes(); ++ ++ /** ++ * Get an XML String representation of this message. ++ * ++ * @return XML string representing the body message ++ */ ++ public abstract String toXML(); ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Package-private methods: ++ ++ /** ++ * Returns the qualified name of the root/wrapper element. ++ * ++ * @return qualified name ++ */ ++ static BodyQName getBodyQName() { ++ return BodyQName.createBOSH("body"); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AbstractIntegerAttr.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AbstractIntegerAttr.java +new file mode 100644 +index 0000000..1b827f9 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AbstractIntegerAttr.java +@@ -0,0 +1,97 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * Abstract base class for attribute implementations based on {@code Integer} ++ * types. Additional support for parsing of integer values from their ++ * {@code String} representations as well as callback handling of value ++ * validity checks are also provided. ++ */ ++abstract class AbstractIntegerAttr extends AbstractAttr { ++ ++ /** ++ * Creates a new attribute object. ++ * ++ * @param val attribute value ++ * @throws BOSHException on parse or validation failure ++ */ ++ protected AbstractIntegerAttr(final int val) throws BOSHException { ++ super(Integer.valueOf(val)); ++ } ++ ++ /** ++ * Creates a new attribute object. ++ * ++ * @param val attribute value in string form ++ * @throws BOSHException on parse or validation failure ++ */ ++ protected AbstractIntegerAttr(final String val) throws BOSHException { ++ super(parseInt(val)); ++ } ++ ++ /** ++ * Utility method intended to be called by concrete implementation ++ * classes from within the {@code check()} method when the concrete ++ * class needs to ensure that the integer value does not drop below ++ * the specified minimum value. ++ * ++ * @param minVal minimum value to allow ++ * @throws BOSHException if the integer value is below the specific ++ * minimum ++ */ ++ protected final void checkMinValue(int minVal) throws BOSHException { ++ int intVal = getValue(); ++ if (intVal < minVal) { ++ throw(new BOSHException( ++ "Illegal attribute value '" + intVal + "' provided. " ++ + "Must be >= " + minVal)); ++ } ++ } ++ ++ /** ++ * Utility method to parse a {@code String} into an {@code Integer}, ++ * converting any possible {@code NumberFormatException} thrown into ++ * a {@code BOSHException}. ++ * ++ * @param str string to parse ++ * @return integer value ++ * @throws BOSHException on {@code NumberFormatException} ++ */ ++ private static int parseInt(final String str) throws BOSHException { ++ try { ++ return Integer.parseInt(str); ++ } catch (NumberFormatException nfx) { ++ throw(new BOSHException( ++ "Could not parse an integer from the value provided: " ++ + str, ++ nfx)); ++ } ++ } ++ ++ /** ++ * Returns the native {@code int} value of the underlying {@code Integer}. ++ * Will throw {@code NullPointerException} if the underlying ++ * integer was {@code null}. ++ * ++ * @return native {@code int} value ++ */ ++ public int intValue() { ++ return getValue().intValue(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/ApacheHTTPResponse.java b/external/asmack/build/src/trunk/com/kenai/jbosh/ApacheHTTPResponse.java +new file mode 100644 +index 0000000..9f6731f +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/ApacheHTTPResponse.java +@@ -0,0 +1,253 @@ ++/* ++ * Copyright 2009 Guenther Niess ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.io.IOException; ++import java.util.concurrent.locks.Lock; ++import java.util.concurrent.locks.ReentrantLock; ++ ++import org.apache.http.HttpEntity; ++import org.apache.http.HttpResponse; ++import org.apache.http.client.HttpClient; ++import org.apache.http.client.methods.HttpPost; ++import org.apache.http.entity.ByteArrayEntity; ++ ++import org.apache.http.protocol.BasicHttpContext; ++import org.apache.http.protocol.HttpContext; ++import org.apache.http.util.EntityUtils; ++ ++final class ApacheHTTPResponse implements HTTPResponse { ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Constants: ++ ++ /** ++ * Name of the accept encoding header. ++ */ ++ private static final String ACCEPT_ENCODING = "Accept-Encoding"; ++ ++ /** ++ * Value to use for the ACCEPT_ENCODING header. ++ */ ++ private static final String ACCEPT_ENCODING_VAL = ++ ZLIBCodec.getID() + ", " + GZIPCodec.getID(); ++ ++ /** ++ * Name of the character set to encode the body to/from. ++ */ ++ private static final String CHARSET = "UTF-8"; ++ ++ /** ++ * Content type to use when transmitting the body data. ++ */ ++ private static final String CONTENT_TYPE = "text/xml; charset=utf-8"; ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Class variables: ++ ++ /** ++ * Lock used for internal synchronization. ++ */ ++ private final Lock lock = new ReentrantLock(); ++ ++ /** ++ * The execution state of an HTTP process. ++ */ ++ private final HttpContext context; ++ ++ /** ++ * HttpClient instance to use to communicate. ++ */ ++ private final HttpClient client; ++ ++ /** ++ * The HTTP POST request is sent to the server. ++ */ ++ private final HttpPost post; ++ ++ /** ++ * A flag which indicates if the transmission was already done. ++ */ ++ private boolean sent; ++ ++ /** ++ * Exception to throw when the response data is attempted to be accessed, ++ * or {@code null} if no exception should be thrown. ++ */ ++ private BOSHException toThrow; ++ ++ /** ++ * The response body which was received from the server or {@code null} ++ * if that has not yet happened. ++ */ ++ private AbstractBody body; ++ ++ /** ++ * The HTTP response status code. ++ */ ++ private int statusCode; ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Constructors: ++ ++ /** ++ * Create and send a new request to the upstream connection manager, ++ * providing deferred access to the results to be returned. ++ * ++ * @param client client instance to use when sending the request ++ * @param cfg client configuration ++ * @param params connection manager parameters from the session creation ++ * response, or {@code null} if the session has not yet been established ++ * @param request body of the client request ++ */ ++ ApacheHTTPResponse( ++ final HttpClient client, ++ final BOSHClientConfig cfg, ++ final CMSessionParams params, ++ final AbstractBody request) { ++ super(); ++ this.client = client; ++ this.context = new BasicHttpContext(); ++ this.post = new HttpPost(cfg.getURI().toString()); ++ this.sent = false; ++ ++ try { ++ String xml = request.toXML(); ++ byte[] data = xml.getBytes(CHARSET); ++ ++ String encoding = null; ++ if (cfg.isCompressionEnabled() && params != null) { ++ AttrAccept accept = params.getAccept(); ++ if (accept != null) { ++ if (accept.isAccepted(ZLIBCodec.getID())) { ++ encoding = ZLIBCodec.getID(); ++ data = ZLIBCodec.encode(data); ++ } else if (accept.isAccepted(GZIPCodec.getID())) { ++ encoding = GZIPCodec.getID(); ++ data = GZIPCodec.encode(data); ++ } ++ } ++ } ++ ++ ByteArrayEntity entity = new ByteArrayEntity(data); ++ entity.setContentType(CONTENT_TYPE); ++ if (encoding != null) { ++ entity.setContentEncoding(encoding); ++ } ++ post.setEntity(entity); ++ if (cfg.isCompressionEnabled()) { ++ post.setHeader(ACCEPT_ENCODING, ACCEPT_ENCODING_VAL); ++ } ++ } catch (Exception e) { ++ toThrow = new BOSHException("Could not generate request", e); ++ } ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // HTTPResponse interface methods: ++ ++ /** ++ * Abort the client transmission and response processing. ++ */ ++ public void abort() { ++ if (post != null) { ++ post.abort(); ++ toThrow = new BOSHException("HTTP request aborted"); ++ } ++ } ++ ++ /** ++ * Wait for and then return the response body. ++ * ++ * @return body of the response ++ * @throws InterruptedException if interrupted while awaiting the response ++ * @throws BOSHException on communication failure ++ */ ++ public AbstractBody getBody() throws InterruptedException, BOSHException { ++ if (toThrow != null) { ++ throw(toThrow); ++ } ++ lock.lock(); ++ try { ++ if (!sent) { ++ awaitResponse(); ++ } ++ } finally { ++ lock.unlock(); ++ } ++ return body; ++ } ++ ++ /** ++ * Wait for and then return the response HTTP status code. ++ * ++ * @return HTTP status code of the response ++ * @throws InterruptedException if interrupted while awaiting the response ++ * @throws BOSHException on communication failure ++ */ ++ public int getHTTPStatus() throws InterruptedException, BOSHException { ++ if (toThrow != null) { ++ throw(toThrow); ++ } ++ lock.lock(); ++ try { ++ if (!sent) { ++ awaitResponse(); ++ } ++ } finally { ++ lock.unlock(); ++ } ++ return statusCode; ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Package-private methods: ++ ++ /** ++ * Await the response, storing the result in the instance variables of ++ * this class when they arrive. ++ * ++ * @throws InterruptedException if interrupted while awaiting the response ++ * @throws BOSHException on communication failure ++ */ ++ private synchronized void awaitResponse() throws BOSHException { ++ HttpEntity entity = null; ++ try { ++ HttpResponse httpResp = client.execute(post, context); ++ entity = httpResp.getEntity(); ++ byte[] data = EntityUtils.toByteArray(entity); ++ String encoding = entity.getContentEncoding() != null ? ++ entity.getContentEncoding().getValue() : ++ null; ++ if (ZLIBCodec.getID().equalsIgnoreCase(encoding)) { ++ data = ZLIBCodec.decode(data); ++ } else if (GZIPCodec.getID().equalsIgnoreCase(encoding)) { ++ data = GZIPCodec.decode(data); ++ } ++ body = StaticBody.fromString(new String(data, CHARSET)); ++ statusCode = httpResp.getStatusLine().getStatusCode(); ++ sent = true; ++ } catch (IOException iox) { ++ abort(); ++ toThrow = new BOSHException("Could not obtain response", iox); ++ throw(toThrow); ++ } catch (RuntimeException ex) { ++ abort(); ++ throw(ex); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/ApacheHTTPSender.java b/external/asmack/build/src/trunk/com/kenai/jbosh/ApacheHTTPSender.java +new file mode 100644 +index 0000000..b3d3c93 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/ApacheHTTPSender.java +@@ -0,0 +1,156 @@ ++/* ++ * Copyright 2009 Guenther Niess ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.util.concurrent.locks.Lock; ++import java.util.concurrent.locks.ReentrantLock; ++ ++import org.apache.http.HttpHost; ++import org.apache.http.HttpVersion; ++import org.apache.http.client.HttpClient; ++import org.apache.http.conn.ClientConnectionManager; ++import org.apache.http.conn.params.ConnManagerParams; ++import org.apache.http.conn.params.ConnRoutePNames; ++import org.apache.http.conn.scheme.PlainSocketFactory; ++import org.apache.http.conn.scheme.Scheme; ++import org.apache.http.conn.scheme.SchemeRegistry; ++import org.apache.http.conn.ssl.SSLSocketFactory; ++import org.apache.http.impl.client.DefaultHttpClient; ++import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; ++import org.apache.http.params.BasicHttpParams; ++import org.apache.http.params.HttpParams; ++import org.apache.http.params.HttpProtocolParams; ++ ++/** ++ * Implementation of the {@code HTTPSender} interface which uses the ++ * Apache HttpClient API to send messages to the connection manager. ++ */ ++final class ApacheHTTPSender implements HTTPSender { ++ ++ /** ++ * Lock used for internal synchronization. ++ */ ++ private final Lock lock = new ReentrantLock(); ++ ++ /** ++ * Session configuration. ++ */ ++ private BOSHClientConfig cfg; ++ ++ /** ++ * HttpClient instance to use to communicate. ++ */ ++ private HttpClient httpClient; ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Constructors: ++ ++ /** ++ * Prevent construction apart from our package. ++ */ ++ ApacheHTTPSender() { ++ // Load Apache HTTP client class ++ HttpClient.class.getName(); ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // HTTPSender interface methods: ++ ++ /** ++ * {@inheritDoc} ++ */ ++ public void init(final BOSHClientConfig session) { ++ lock.lock(); ++ try { ++ cfg = session; ++ httpClient = initHttpClient(session); ++ } finally { ++ lock.unlock(); ++ } ++ } ++ ++ /** ++ * {@inheritDoc} ++ */ ++ public void destroy() { ++ lock.lock(); ++ try { ++ if (httpClient != null) { ++ httpClient.getConnectionManager().shutdown(); ++ } ++ } finally { ++ cfg = null; ++ httpClient = null; ++ lock.unlock(); ++ } ++ } ++ ++ /** ++ * {@inheritDoc} ++ */ ++ public HTTPResponse send( ++ final CMSessionParams params, ++ final AbstractBody body) { ++ HttpClient mClient; ++ BOSHClientConfig mCfg; ++ lock.lock(); ++ try { ++ if (httpClient == null) { ++ httpClient = initHttpClient(cfg); ++ } ++ mClient = httpClient; ++ mCfg = cfg; ++ } finally { ++ lock.unlock(); ++ } ++ return new ApacheHTTPResponse(mClient, mCfg, params, body); ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Package-private methods: ++ ++ private synchronized HttpClient initHttpClient(final BOSHClientConfig config) { ++ // Create and initialize HTTP parameters ++ HttpParams params = new BasicHttpParams(); ++ ConnManagerParams.setMaxTotalConnections(params, 100); ++ HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); ++ HttpProtocolParams.setUseExpectContinue(params, false); ++ if (config != null && ++ config.getProxyHost() != null && ++ config.getProxyPort() != 0) { ++ HttpHost proxy = new HttpHost( ++ config.getProxyHost(), ++ config.getProxyPort()); ++ params.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); ++ } ++ ++ // Create and initialize scheme registry ++ SchemeRegistry schemeRegistry = new SchemeRegistry(); ++ schemeRegistry.register( ++ new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); ++ SSLSocketFactory sslFactory = SSLSocketFactory.getSocketFactory(); ++ sslFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); ++ schemeRegistry.register( ++ new Scheme("https", sslFactory, 443)); ++ ++ // Create an HttpClient with the ThreadSafeClientConnManager. ++ // This connection manager must be used if more than one thread will ++ // be using the HttpClient. ++ ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry); ++ return new DefaultHttpClient(cm, params); ++ } ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrAccept.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrAccept.java +new file mode 100644 +index 0000000..4f767df +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrAccept.java +@@ -0,0 +1,74 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * Data type representing the getValue of the {@code accept} attribute of the ++ * {@code bosh} element. ++ */ ++final class AttrAccept extends AbstractAttr { ++ ++ /** ++ * Array of the accepted encodings. ++ */ ++ private final String[] encodings; ++ ++ /** ++ * Creates a new attribute object. ++ * ++ * @param val attribute getValue ++ * @throws BOSHException on parse or validation failure ++ */ ++ private AttrAccept(final String val) { ++ super(val); ++ encodings = val.split("[\\s,]+"); ++ } ++ ++ /** ++ * Creates a new attribute instance from the provided String. ++ * ++ * @param str string representation of the attribute ++ * @return attribute instance or {@code null} if provided string is ++ * {@code null} ++ * @throws BOSHException on parse or validation failure ++ */ ++ static AttrAccept createFromString(final String str) ++ throws BOSHException { ++ if (str == null) { ++ return null; ++ } else { ++ return new AttrAccept(str); ++ } ++ } ++ ++ /** ++ * Determines whether or not the specified encoding is supported. ++ * ++ * @param name encoding name ++ * @result {@code true} if the encoding is accepted, {@code false} ++ * otherwise ++ */ ++ boolean isAccepted(final String name) { ++ for (String str : encodings) { ++ if (str.equalsIgnoreCase(name)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrAck.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrAck.java +new file mode 100644 +index 0000000..6cfe22b +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrAck.java +@@ -0,0 +1,52 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * Data type representing the getValue of the {@code ack} attribute of the ++ * {@code bosh} element. ++ */ ++final class AttrAck extends AbstractAttr { ++ ++ /** ++ * Creates a new attribute object. ++ * ++ * @param val attribute getValue ++ * @throws BOSHException on parse or validation failure ++ */ ++ private AttrAck(final String val) throws BOSHException { ++ super(val); ++ } ++ ++ /** ++ * Creates a new attribute instance from the provided String. ++ * ++ * @param str string representation of the attribute ++ * @return attribute instance or {@code null} if provided string is ++ * {@code null} ++ * @throws BOSHException on parse or validation failure ++ */ ++ static AttrAck createFromString(final String str) ++ throws BOSHException { ++ if (str == null) { ++ return null; ++ } else { ++ return new AttrAck(str); ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrCharsets.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrCharsets.java +new file mode 100644 +index 0000000..45ce78c +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrCharsets.java +@@ -0,0 +1,71 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * Data type representing the getValue of the {@code charsets} attribute of the ++ * {@code bosh} element. ++ */ ++final class AttrCharsets extends AbstractAttr { ++ ++ /** ++ * Array of the accepted character sets. ++ */ ++ private final String[] charsets; ++ ++ /** ++ * Creates a new attribute object. ++ * ++ * @param val attribute getValue ++ */ ++ private AttrCharsets(final String val) { ++ super(val); ++ charsets = val.split("\\ +"); ++ } ++ ++ /** ++ * Creates a new attribute instance from the provided String. ++ * ++ * @param str string representation of the attribute ++ * @return attribute instance or {@code null} if provided string is ++ * {@code null} ++ */ ++ static AttrCharsets createFromString(final String str) { ++ if (str == null) { ++ return null; ++ } else { ++ return new AttrCharsets(str); ++ } ++ } ++ ++ /** ++ * Determines whether or not the specified charset is supported. ++ * ++ * @param name encoding name ++ * @result {@code true} if the encoding is accepted, {@code false} ++ * otherwise ++ */ ++ boolean isAccepted(final String name) { ++ for (String str : charsets) { ++ if (str.equalsIgnoreCase(name)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrHold.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrHold.java +new file mode 100644 +index 0000000..56f21dd +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrHold.java +@@ -0,0 +1,53 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * Data type representing the getValue of the {@code hold} attribute of the ++ * {@code bosh} element. ++ */ ++final class AttrHold extends AbstractIntegerAttr { ++ ++ /** ++ * Creates a new attribute object. ++ * ++ * @param val attribute getValue ++ * @throws BOSHException on parse or validation failure ++ */ ++ private AttrHold(final String val) throws BOSHException { ++ super(val); ++ checkMinValue(0); ++ } ++ ++ /** ++ * Creates a new attribute instance from the provided String. ++ * ++ * @param str string representation of the attribute ++ * @return attribute instance or {@code null} if provided string is ++ * {@code null} ++ * @throws BOSHException on parse or validation failure ++ */ ++ static AttrHold createFromString(final String str) ++ throws BOSHException { ++ if (str == null) { ++ return null; ++ } else { ++ return new AttrHold(str); ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrInactivity.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrInactivity.java +new file mode 100644 +index 0000000..14ab7d4 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrInactivity.java +@@ -0,0 +1,53 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * Data type representing the value of the {@code inactivity} attribute of the ++ * {@code bosh} element. ++ */ ++final class AttrInactivity extends AbstractIntegerAttr { ++ ++ /** ++ * Creates a new attribute object. ++ * ++ * @param val attribute value ++ * @throws BOSHException on parse or validation failure ++ */ ++ private AttrInactivity(final String val) throws BOSHException { ++ super(val); ++ checkMinValue(0); ++ } ++ ++ /** ++ * Creates a new attribute instance from the provided String. ++ * ++ * @param str string representation of the attribute ++ * @return instance of the attribute for the specified string, or ++ * {@code null} if input string is {@code null} ++ * @throws BOSHException on parse or validation failure ++ */ ++ static AttrInactivity createFromString(final String str) ++ throws BOSHException { ++ if (str == null) { ++ return null; ++ } else { ++ return new AttrInactivity(str); ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrMaxPause.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrMaxPause.java +new file mode 100644 +index 0000000..8d1d98b +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrMaxPause.java +@@ -0,0 +1,65 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.util.concurrent.TimeUnit; ++ ++/** ++ * Data type representing the getValue of the {@code maxpause} attribute of the ++ * {@code bosh} element. ++ */ ++final class AttrMaxPause extends AbstractIntegerAttr { ++ ++ /** ++ * Creates a new attribute object. ++ * ++ * @param val attribute getValue ++ * @throws BOSHException on parse or validation failure ++ */ ++ private AttrMaxPause(final String val) throws BOSHException { ++ super(val); ++ checkMinValue(1); ++ } ++ ++ /** ++ * Creates a new attribute instance from the provided String. ++ * ++ * @param str string representation of the attribute ++ * @return attribute instance or {@code null} if provided string is ++ * {@code null} ++ * @throws BOSHException on parse or validation failure ++ */ ++ static AttrMaxPause createFromString(final String str) ++ throws BOSHException { ++ if (str == null) { ++ return null; ++ } else { ++ return new AttrMaxPause(str); ++ } ++ } ++ ++ /** ++ * Get the max pause time in milliseconds. ++ * ++ * @return pause tme in milliseconds ++ */ ++ public int getInMilliseconds() { ++ return (int) TimeUnit.MILLISECONDS.convert( ++ intValue(), TimeUnit.SECONDS); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrPause.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrPause.java +new file mode 100644 +index 0000000..5fb3282 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrPause.java +@@ -0,0 +1,65 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.util.concurrent.TimeUnit; ++ ++/** ++ * Data type representing the getValue of the {@code pause} attribute of the ++ * {@code bosh} element. ++ */ ++final class AttrPause extends AbstractIntegerAttr { ++ ++ /** ++ * Creates a new attribute object. ++ * ++ * @param val attribute getValue ++ * @throws BOSHException on parse or validation failure ++ */ ++ private AttrPause(final String val) throws BOSHException { ++ super(val); ++ checkMinValue(1); ++ } ++ ++ /** ++ * Creates a new attribute instance from the provided String. ++ * ++ * @param str string representation of the attribute ++ * @return attribute instance or {@code null} if provided string is ++ * {@code null} ++ * @throws BOSHException on parse or validation failure ++ */ ++ static AttrPause createFromString(final String str) ++ throws BOSHException { ++ if (str == null) { ++ return null; ++ } else { ++ return new AttrPause(str); ++ } ++ } ++ ++ /** ++ * Get the pause time in milliseconds. ++ * ++ * @return pause tme in milliseconds ++ */ ++ public int getInMilliseconds() { ++ return (int) TimeUnit.MILLISECONDS.convert( ++ intValue(), TimeUnit.SECONDS); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrPolling.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrPolling.java +new file mode 100644 +index 0000000..3f0b08d +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrPolling.java +@@ -0,0 +1,65 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.util.concurrent.TimeUnit; ++ ++/** ++ * Data type representing the getValue of the {@code polling} attribute of the ++ * {@code bosh} element. ++ */ ++final class AttrPolling extends AbstractIntegerAttr { ++ ++ /** ++ * Creates a new attribute object. ++ * ++ * @param val attribute getValue ++ * @throws BOSHException on parse or validation failure ++ */ ++ private AttrPolling(final String str) throws BOSHException { ++ super(str); ++ checkMinValue(0); ++ } ++ ++ /** ++ * Creates a new attribute instance from the provided String. ++ * ++ * @param str string representation of the attribute ++ * @return instance of the attribute for the specified string, or ++ * {@code null} if input string is {@code null} ++ * @throws BOSHException on parse or validation failure ++ */ ++ static AttrPolling createFromString(final String str) ++ throws BOSHException { ++ if (str == null) { ++ return null; ++ } else { ++ return new AttrPolling(str); ++ } ++ } ++ ++ /** ++ * Get the polling interval in milliseconds. ++ * ++ * @return polling interval in milliseconds ++ */ ++ public int getInMilliseconds() { ++ return (int) TimeUnit.MILLISECONDS.convert( ++ intValue(), TimeUnit.SECONDS); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrRequests.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrRequests.java +new file mode 100644 +index 0000000..bfdc529 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrRequests.java +@@ -0,0 +1,53 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * Data type representing the value of the {@code requests} attribute of the ++ * {@code bosh} element. ++ */ ++final class AttrRequests extends AbstractIntegerAttr { ++ ++ /** ++ * Creates a new attribute object. ++ * ++ * @param val attribute value ++ * @throws BOSHException on parse or validation failure ++ */ ++ private AttrRequests(final String val) throws BOSHException { ++ super(val); ++ checkMinValue(1); ++ } ++ ++ /** ++ * Creates a new attribute instance from the provided String. ++ * ++ * @param str string representation of the attribute ++ * @return instance of the attribute for the specified string, or ++ * {@code null} if input string is {@code null} ++ * @throws BOSHException on parse or validation failure ++ */ ++ static AttrRequests createFromString(final String str) ++ throws BOSHException { ++ if (str == null) { ++ return null; ++ } else { ++ return new AttrRequests(str); ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrSessionID.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrSessionID.java +new file mode 100644 +index 0000000..1998968 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrSessionID.java +@@ -0,0 +1,44 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * Data type representing the getValue of the {@code sid} attribute of the ++ * {@code bosh} element. ++ */ ++final class AttrSessionID extends AbstractAttr { ++ ++ /** ++ * Creates a new attribute object. ++ * ++ * @param val attribute getValue ++ */ ++ private AttrSessionID(final String val) { ++ super(val); ++ } ++ ++ /** ++ * Creates a new attribute instance from the provided String. ++ * ++ * @param str string representation of the attribute ++ * @return attribute instance ++ */ ++ static AttrSessionID createFromString(final String str) { ++ return new AttrSessionID(str); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrVersion.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrVersion.java +new file mode 100644 +index 0000000..9396e3b +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrVersion.java +@@ -0,0 +1,165 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * Data type representing the getValue of the {@code ver} attribute of the ++ * {@code bosh} element. ++ */ ++final class AttrVersion extends AbstractAttr implements Comparable { ++ ++ /** ++ * Default value if none is provided. ++ */ ++ private static final AttrVersion DEFAULT; ++ static { ++ try { ++ DEFAULT = createFromString("1.8"); ++ } catch (BOSHException boshx) { ++ throw(new IllegalStateException(boshx)); ++ } ++ } ++ ++ /** ++ * Major portion of the version. ++ */ ++ private final int major; ++ ++ /** ++ * Minor portion of the version. ++ */ ++ private final int minor; ++ ++ /** ++ * Creates a new attribute object. ++ * ++ * @param val attribute getValue ++ * @throws BOSHException on parse or validation failure ++ */ ++ private AttrVersion(final String val) throws BOSHException { ++ super(val); ++ ++ int idx = val.indexOf('.'); ++ if (idx <= 0) { ++ throw(new BOSHException( ++ "Illegal ver attribute value (not in major.minor form): " ++ + val)); ++ } ++ ++ String majorStr = val.substring(0, idx); ++ try { ++ major = Integer.parseInt(majorStr); ++ } catch (NumberFormatException nfx) { ++ throw(new BOSHException( ++ "Could not parse ver attribute value (major ver): " ++ + majorStr, ++ nfx)); ++ } ++ if (major < 0) { ++ throw(new BOSHException( ++ "Major version may not be < 0")); ++ } ++ ++ String minorStr = val.substring(idx + 1); ++ try { ++ minor = Integer.parseInt(minorStr); ++ } catch (NumberFormatException nfx) { ++ throw(new BOSHException( ++ "Could not parse ver attribute value (minor ver): " ++ + minorStr, ++ nfx)); ++ } ++ if (minor < 0) { ++ throw(new BOSHException( ++ "Minor version may not be < 0")); ++ } ++ } ++ ++ /** ++ * Get the version of specifcation that we support. ++ * ++ * @return max spec version the code supports ++ */ ++ static AttrVersion getSupportedVersion() { ++ return DEFAULT; ++ } ++ ++ /** ++ * Creates a new attribute instance from the provided String. ++ * ++ * @param str string representation of the attribute ++ * @return attribute instance or {@code null} if provided string is ++ * {@code null} ++ * @throws BOSHException on parse or validation failure ++ */ ++ static AttrVersion createFromString(final String str) ++ throws BOSHException { ++ if (str == null) { ++ return null; ++ } else { ++ return new AttrVersion(str); ++ } ++ } ++ ++ /** ++ * Returns the 'major' portion of the version number. ++ * ++ * @return major digits only ++ */ ++ int getMajor() { ++ return major; ++ } ++ ++ /** ++ * Returns the 'minor' portion of the version number. ++ * ++ * @return minor digits only ++ */ ++ int getMinor() { ++ return minor; ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Comparable interface: ++ ++ /** ++ * {@inheritDoc} ++ * ++ * @param otherObj object to compare to ++ * @return -1, 0, or 1 ++ */ ++ @Override ++ public int compareTo(final Object otherObj) { ++ if (otherObj instanceof AttrVersion) { ++ AttrVersion other = (AttrVersion) otherObj; ++ if (major < other.major) { ++ return -1; ++ } else if (major > other.major) { ++ return 1; ++ } else if (minor < other.minor) { ++ return -1; ++ } else if (minor > other.minor) { ++ return 1; ++ } else { ++ return 0; ++ } ++ } else { ++ return 0; ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrWait.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrWait.java +new file mode 100644 +index 0000000..d2c95f7 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrWait.java +@@ -0,0 +1,53 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * Data type representing the getValue of the {@code wait} attribute of the ++ * {@code bosh} element. ++ */ ++final class AttrWait extends AbstractIntegerAttr { ++ ++ /** ++ * Creates a new attribute object. ++ * ++ * @param val attribute getValue ++ * @throws BOSHException on parse or validation failure ++ */ ++ private AttrWait(final String val) throws BOSHException { ++ super(val); ++ checkMinValue(1); ++ } ++ ++ /** ++ * Creates a new attribute instance from the provided String. ++ * ++ * @param str string representation of the attribute ++ * @return attribute instance or {@code null} if provided string is ++ * {@code null} ++ * @throws BOSHException on parse or validation failure ++ */ ++ static AttrWait createFromString(final String str) ++ throws BOSHException { ++ if (str == null) { ++ return null; ++ } else { ++ return new AttrWait(str); ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/Attributes.java b/external/asmack/build/src/trunk/com/kenai/jbosh/Attributes.java +new file mode 100644 +index 0000000..d01541e +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/Attributes.java +@@ -0,0 +1,64 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import javax.xml.XMLConstants; ++ ++/** ++ * Class containing constants for attribute definitions used by the ++ * XEP-0124 specification. We shouldn't need to expose these outside ++ * our package, since nobody else should be needing to worry about ++ * them. ++ */ ++final class Attributes { ++ ++ /** ++ * Private constructor to prevent construction of library class. ++ */ ++ private Attributes() { ++ super(); ++ } ++ ++ static final BodyQName ACCEPT = BodyQName.createBOSH("accept"); ++ static final BodyQName AUTHID = BodyQName.createBOSH("authid"); ++ static final BodyQName ACK = BodyQName.createBOSH("ack"); ++ static final BodyQName CHARSETS = BodyQName.createBOSH("charsets"); ++ static final BodyQName CONDITION = BodyQName.createBOSH("condition"); ++ static final BodyQName CONTENT = BodyQName.createBOSH("content"); ++ static final BodyQName FROM = BodyQName.createBOSH("from"); ++ static final BodyQName HOLD = BodyQName.createBOSH("hold"); ++ static final BodyQName INACTIVITY = BodyQName.createBOSH("inactivity"); ++ static final BodyQName KEY = BodyQName.createBOSH("key"); ++ static final BodyQName MAXPAUSE = BodyQName.createBOSH("maxpause"); ++ static final BodyQName NEWKEY = BodyQName.createBOSH("newkey"); ++ static final BodyQName PAUSE = BodyQName.createBOSH("pause"); ++ static final BodyQName POLLING = BodyQName.createBOSH("polling"); ++ static final BodyQName REPORT = BodyQName.createBOSH("report"); ++ static final BodyQName REQUESTS = BodyQName.createBOSH("requests"); ++ static final BodyQName RID = BodyQName.createBOSH("rid"); ++ static final BodyQName ROUTE = BodyQName.createBOSH("route"); ++ static final BodyQName SECURE = BodyQName.createBOSH("secure"); ++ static final BodyQName SID = BodyQName.createBOSH("sid"); ++ static final BodyQName STREAM = BodyQName.createBOSH("stream"); ++ static final BodyQName TIME = BodyQName.createBOSH("time"); ++ static final BodyQName TO = BodyQName.createBOSH("to"); ++ static final BodyQName TYPE = BodyQName.createBOSH("type"); ++ static final BodyQName VER = BodyQName.createBOSH("ver"); ++ static final BodyQName WAIT = BodyQName.createBOSH("wait"); ++ static final BodyQName XML_LANG = ++ BodyQName.createWithPrefix(XMLConstants.XML_NS_URI, "lang", "xml"); ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClient.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClient.java +new file mode 100644 +index 0000000..b96d188 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClient.java +@@ -0,0 +1,1536 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import com.kenai.jbosh.ComposableBody.Builder; ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Queue; ++import java.util.Set; ++import java.util.SortedSet; ++import java.util.TreeSet; ++import java.util.concurrent.CopyOnWriteArraySet; ++import java.util.concurrent.Executors; ++import java.util.concurrent.RejectedExecutionException; ++import java.util.concurrent.ScheduledExecutorService; ++import java.util.concurrent.ScheduledFuture; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.atomic.AtomicReference; ++import java.util.concurrent.locks.Condition; ++import java.util.concurrent.locks.ReentrantLock; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++/** ++ * BOSH Client session instance. Each communication session with a remote ++ * connection manager is represented and handled by an instance of this ++ * class. This is the main entry point for client-side communications. ++ * To create a new session, a client configuration must first be created ++ * and then used to create a client instance: ++ *

++ * BOSHClientConfig cfg = BOSHClientConfig.Builder.create(
++ *         "http://server:1234/httpbind", "jabber.org")
++ *     .setFrom("user@jabber.org")
++ *     .build();
++ * BOSHClient client = BOSHClient.create(cfg);
++ * 
++ * Additional client configuration options are available. See the ++ * {@code BOSHClientConfig.Builder} class for more information. ++ *

++ * Once a {@code BOSHClient} instance has been created, communication with ++ * the remote connection manager can begin. No attempt will be made to ++ * establish a connection to the connection manager until the first call ++ * is made to the {@code send(ComposableBody)} method. Note that it is ++ * possible to send an empty body to cause an immediate connection attempt ++ * to the connection manager. Sending an empty message would look like ++ * the following: ++ *

++ * client.send(ComposableBody.builder().build());
++ * 
++ * For more information on creating body messages with content, see the ++ * {@code ComposableBody.Builder} class documentation. ++ *

++ * Once a session has been successfully started, the client instance can be ++ * used to send arbitrary payload data. All aspects of the BOSH ++ * protocol involving setting and processing attributes in the BOSH ++ * namespace will be handled by the client code transparently and behind the ++ * scenes. The user of the client instance can therefore concentrate ++ * entirely on the content of the message payload, leaving the semantics of ++ * the BOSH protocol to the client implementation. ++ *

++ * To be notified of incoming messages from the remote connection manager, ++ * a {@code BOSHClientResponseListener} should be added to the client instance. ++ * All incoming messages will be published to all response listeners as they ++ * arrive and are processed. As with the transmission of payload data via ++ * the {@code send(ComposableBody)} method, there is no need to worry about ++ * handling of the BOSH attributes, since this is handled behind the scenes. ++ *

++ * If the connection to the remote connection manager is terminated (either ++ * explicitly or due to a terminal condition of some sort), all connection ++ * listeners will be notified. After the connection has been closed, the ++ * client instance is considered dead and a new one must be created in order ++ * to resume communications with the remote server. ++ *

++ * Instances of this class are thread-safe. ++ * ++ * @see BOSHClientConfig.Builder ++ * @see BOSHClientResponseListener ++ * @see BOSHClientConnListener ++ * @see ComposableBody.Builder ++ */ ++public final class BOSHClient { ++ ++ /** ++ * Logger. ++ */ ++ private static final Logger LOG = Logger.getLogger( ++ BOSHClient.class.getName()); ++ ++ /** ++ * Value of the 'type' attribute used for session termination. ++ */ ++ private static final String TERMINATE = "terminate"; ++ ++ /** ++ * Value of the 'type' attribute used for recoverable errors. ++ */ ++ private static final String ERROR = "error"; ++ ++ /** ++ * Message to use for interrupted exceptions. ++ */ ++ private static final String INTERRUPTED = "Interrupted"; ++ ++ /** ++ * Message used for unhandled exceptions. ++ */ ++ private static final String UNHANDLED = "Unhandled Exception"; ++ ++ /** ++ * Message used whena null listener is detected. ++ */ ++ private static final String NULL_LISTENER = "Listener may not b enull"; ++ ++ /** ++ * Default empty request delay. ++ */ ++ private static final int DEFAULT_EMPTY_REQUEST_DELAY = 100; ++ ++ /** ++ * Amount of time to wait before sending an empty request, in ++ * milliseconds. ++ */ ++ private static final int EMPTY_REQUEST_DELAY = Integer.getInteger( ++ BOSHClient.class.getName() + ".emptyRequestDelay", ++ DEFAULT_EMPTY_REQUEST_DELAY); ++ ++ /** ++ * Default value for the pause margin. ++ */ ++ private static final int DEFAULT_PAUSE_MARGIN = 500; ++ ++ /** ++ * The amount of time in milliseconds which will be reserved as a ++ * safety margin when scheduling empty requests against a maxpause ++ * value. This should give us enough time to build the message ++ * and transport it to the remote host. ++ */ ++ private static final int PAUSE_MARGIN = Integer.getInteger( ++ BOSHClient.class.getName() + ".pauseMargin", ++ DEFAULT_PAUSE_MARGIN); ++ ++ /** ++ * Flag indicating whether or not we want to perform assertions. ++ */ ++ private static final boolean ASSERTIONS; ++ ++ /** ++ * Connection listeners. ++ */ ++ private final Set connListeners = ++ new CopyOnWriteArraySet(); ++ ++ /** ++ * Request listeners. ++ */ ++ private final Set requestListeners = ++ new CopyOnWriteArraySet(); ++ ++ /** ++ * Response listeners. ++ */ ++ private final Set responseListeners = ++ new CopyOnWriteArraySet(); ++ ++ /** ++ * Lock instance. ++ */ ++ private final ReentrantLock lock = new ReentrantLock(); ++ ++ /** ++ * Condition indicating that there are messages to be exchanged. ++ */ ++ private final Condition notEmpty = lock.newCondition(); ++ ++ /** ++ * Condition indicating that there are available slots for sending ++ * messages. ++ */ ++ private final Condition notFull = lock.newCondition(); ++ ++ /** ++ * Condition indicating that there are no outstanding connections. ++ */ ++ private final Condition drained = lock.newCondition(); ++ ++ /** ++ * Session configuration. ++ */ ++ private final BOSHClientConfig cfg; ++ ++ /** ++ * Processor thread runnable instance. ++ */ ++ private final Runnable procRunnable = new Runnable() { ++ /** ++ * Process incoming messages. ++ */ ++ public void run() { ++ processMessages(); ++ } ++ }; ++ ++ /** ++ * Processor thread runnable instance. ++ */ ++ private final Runnable emptyRequestRunnable = new Runnable() { ++ /** ++ * Process incoming messages. ++ */ ++ public void run() { ++ sendEmptyRequest(); ++ } ++ }; ++ ++ /** ++ * HTTPSender instance. ++ */ ++ private final HTTPSender httpSender = ++ new ApacheHTTPSender(); ++ ++ /** ++ * Storage for test hook implementation. ++ */ ++ private final AtomicReference exchInterceptor = ++ new AtomicReference(); ++ ++ /** ++ * Request ID sequence to use for the session. ++ */ ++ private final RequestIDSequence requestIDSeq = new RequestIDSequence(); ++ ++ /** ++ * ScheduledExcecutor to use for deferred tasks. ++ */ ++ private final ScheduledExecutorService schedExec = ++ Executors.newSingleThreadScheduledExecutor(); ++ ++ /************************************************************ ++ * The following vars must be accessed via the lock instance. ++ */ ++ ++ /** ++ * Thread which is used to process responses from the connection ++ * manager. Becomes null when session is terminated. ++ */ ++ private Thread procThread; ++ ++ /** ++ * Future for sending a deferred empty request, if needed. ++ */ ++ private ScheduledFuture emptyRequestFuture; ++ ++ /** ++ * Connection Manager session parameters. Only available when in a ++ * connected state. ++ */ ++ private CMSessionParams cmParams; ++ ++ /** ++ * List of active/outstanding requests. ++ */ ++ private Queue exchanges = new LinkedList(); ++ ++ /** ++ * Set of RIDs which have been received, for the purpose of sending ++ * response acknowledgements. ++ */ ++ private SortedSet pendingResponseAcks = new TreeSet(); ++ ++ /** ++ * The highest RID that we've already received a response for. This value ++ * is used to implement response acks. ++ */ ++ private Long responseAck = Long.valueOf(-1L); ++ ++ /** ++ * List of requests which have been made but not yet acknowledged. This ++ * list remains unpopulated if the CM is not acking requests. ++ */ ++ private List pendingRequestAcks = ++ new ArrayList(); ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Classes: ++ ++ /** ++ * Class used in testing to dynamically manipulate received exchanges ++ * at test runtime. ++ */ ++ abstract static class ExchangeInterceptor { ++ /** ++ * Limit construction. ++ */ ++ ExchangeInterceptor() { ++ // Empty; ++ } ++ ++ /** ++ * Hook to manipulate an HTTPExchange as is is about to be processed. ++ * ++ * @param exch original exchange that would be processed ++ * @return replacement exchange instance, or {@code null} to skip ++ * processing of this exchange ++ */ ++ abstract HTTPExchange interceptExchange(final HTTPExchange exch); ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Constructors: ++ ++ /** ++ * Determine whether or not we should perform assertions. Assertions ++ * can be specified via system property explicitly, or defaulted to ++ * the JVM assertions status. ++ */ ++ static { ++ final String prop = ++ BOSHClient.class.getSimpleName() + ".assertionsEnabled"; ++ boolean enabled = false; ++ if (System.getProperty(prop) == null) { ++ assert enabled = true; ++ } else { ++ enabled = Boolean.getBoolean(prop); ++ } ++ ASSERTIONS = enabled; ++ } ++ ++ /** ++ * Prevent direct construction. ++ */ ++ private BOSHClient(final BOSHClientConfig sessCfg) { ++ cfg = sessCfg; ++ init(); ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Public methods: ++ ++ /** ++ * Create a new BOSH client session using the client configuration ++ * information provided. ++ * ++ * @param clientCfg session configuration ++ * @return BOSH session instance ++ */ ++ public static BOSHClient create(final BOSHClientConfig clientCfg) { ++ if (clientCfg == null) { ++ throw(new IllegalArgumentException( ++ "Client configuration may not be null")); ++ } ++ return new BOSHClient(clientCfg); ++ } ++ ++ /** ++ * Get the client configuration that was used to create this client ++ * instance. ++ * ++ * @return client configuration ++ */ ++ public BOSHClientConfig getBOSHClientConfig() { ++ return cfg; ++ } ++ ++ /** ++ * Adds a connection listener to the session. ++ * ++ * @param listener connection listener to add, if not already added ++ */ ++ public void addBOSHClientConnListener( ++ final BOSHClientConnListener listener) { ++ if (listener == null) { ++ throw(new IllegalArgumentException(NULL_LISTENER)); ++ } ++ connListeners.add(listener); ++ } ++ ++ /** ++ * Removes a connection listener from the session. ++ * ++ * @param listener connection listener to remove, if previously added ++ */ ++ public void removeBOSHClientConnListener( ++ final BOSHClientConnListener listener) { ++ if (listener == null) { ++ throw(new IllegalArgumentException(NULL_LISTENER)); ++ } ++ connListeners.remove(listener); ++ } ++ ++ /** ++ * Adds a request message listener to the session. ++ * ++ * @param listener request listener to add, if not already added ++ */ ++ public void addBOSHClientRequestListener( ++ final BOSHClientRequestListener listener) { ++ if (listener == null) { ++ throw(new IllegalArgumentException(NULL_LISTENER)); ++ } ++ requestListeners.add(listener); ++ } ++ ++ /** ++ * Removes a request message listener from the session, if previously ++ * added. ++ * ++ * @param listener instance to remove ++ */ ++ public void removeBOSHClientRequestListener( ++ final BOSHClientRequestListener listener) { ++ if (listener == null) { ++ throw(new IllegalArgumentException(NULL_LISTENER)); ++ } ++ requestListeners.remove(listener); ++ } ++ ++ /** ++ * Adds a response message listener to the session. ++ * ++ * @param listener response listener to add, if not already added ++ */ ++ public void addBOSHClientResponseListener( ++ final BOSHClientResponseListener listener) { ++ if (listener == null) { ++ throw(new IllegalArgumentException(NULL_LISTENER)); ++ } ++ responseListeners.add(listener); ++ } ++ ++ /** ++ * Removes a response message listener from the session, if previously ++ * added. ++ * ++ * @param listener instance to remove ++ */ ++ public void removeBOSHClientResponseListener( ++ final BOSHClientResponseListener listener) { ++ if (listener == null) { ++ throw(new IllegalArgumentException(NULL_LISTENER)); ++ } ++ responseListeners.remove(listener); ++ } ++ ++ /** ++ * Send the provided message data to the remote connection manager. The ++ * provided message body does not need to have any BOSH-specific attribute ++ * information set. It only needs to contain the actual message payload ++ * that should be delivered to the remote server. ++ *

++ * The first call to this method will result in a connection attempt ++ * to the remote connection manager. Subsequent calls to this method ++ * will block until the underlying session state allows for the message ++ * to be transmitted. In certain scenarios - such as when the maximum ++ * number of outbound connections has been reached - calls to this method ++ * will block for short periods of time. ++ * ++ * @param body message data to send to remote server ++ * @throws BOSHException on message transmission failure ++ */ ++ public void send(final ComposableBody body) throws BOSHException { ++ assertUnlocked(); ++ if (body == null) { ++ throw(new IllegalArgumentException( ++ "Message body may not be null")); ++ } ++ ++ HTTPExchange exch; ++ CMSessionParams params; ++ lock.lock(); ++ try { ++ blockUntilSendable(body); ++ if (!isWorking() && !isTermination(body)) { ++ throw(new BOSHException( ++ "Cannot send message when session is closed")); ++ } ++ ++ long rid = requestIDSeq.getNextRID(); ++ ComposableBody request = body; ++ params = cmParams; ++ if (params == null && exchanges.isEmpty()) { ++ // This is the first message being sent ++ request = applySessionCreationRequest(rid, body); ++ } else { ++ request = applySessionData(rid, body); ++ if (cmParams.isAckingRequests()) { ++ pendingRequestAcks.add(request); ++ } ++ } ++ exch = new HTTPExchange(request); ++ exchanges.add(exch); ++ notEmpty.signalAll(); ++ clearEmptyRequest(); ++ } finally { ++ lock.unlock(); ++ } ++ AbstractBody finalReq = exch.getRequest(); ++ HTTPResponse resp = httpSender.send(params, finalReq); ++ exch.setHTTPResponse(resp); ++ fireRequestSent(finalReq); ++ } ++ ++ /** ++ * Attempt to pause the current session. When supported by the remote ++ * connection manager, pausing the session will result in the connection ++ * manager closing out all outstanding requests (including the pause ++ * request) and increases the inactivity timeout of the session. The ++ * exact value of the temporary timeout is dependent upon the connection ++ * manager. This method should be used if a client encounters an ++ * exceptional temporary situation during which it will be unable to send ++ * requests to the connection manager for a period of time greater than ++ * the maximum inactivity period. ++ * ++ * The session will revert back to it's normal, unpaused state when the ++ * client sends it's next message. ++ * ++ * @return {@code true} if the connection manager supports session pausing, ++ * {@code false} if the connection manager does not support session ++ * pausing or if the session has not yet been established ++ */ ++ public boolean pause() { ++ assertUnlocked(); ++ lock.lock(); ++ AttrMaxPause maxPause = null; ++ try { ++ if (cmParams == null) { ++ return false; ++ } ++ ++ maxPause = cmParams.getMaxPause(); ++ if (maxPause == null) { ++ return false; ++ } ++ } finally { ++ lock.unlock(); ++ } ++ try { ++ send(ComposableBody.builder() ++ .setAttribute(Attributes.PAUSE, maxPause.toString()) ++ .build()); ++ } catch (BOSHException boshx) { ++ LOG.log(Level.FINEST, "Could not send pause", boshx); ++ } ++ return true; ++ } ++ ++ /** ++ * End the BOSH session by disconnecting from the remote BOSH connection ++ * manager. ++ * ++ * @throws BOSHException when termination message cannot be sent ++ */ ++ public void disconnect() throws BOSHException { ++ disconnect(ComposableBody.builder().build()); ++ } ++ ++ /** ++ * End the BOSH session by disconnecting from the remote BOSH connection ++ * manager, sending the provided content in the final connection ++ * termination message. ++ * ++ * @param msg final message to send ++ * @throws BOSHException when termination message cannot be sent ++ */ ++ public void disconnect(final ComposableBody msg) throws BOSHException { ++ if (msg == null) { ++ throw(new IllegalArgumentException( ++ "Message body may not be null")); ++ } ++ ++ Builder builder = msg.rebuild(); ++ builder.setAttribute(Attributes.TYPE, TERMINATE); ++ send(builder.build()); ++ } ++ ++ /** ++ * Forcibly close this client session instance. The preferred mechanism ++ * to close the connection is to send a disconnect message and wait for ++ * organic termination. Calling this method simply shuts down the local ++ * session without sending a termination message, releasing all resources ++ * associated with the session. ++ */ ++ public void close() { ++ dispose(new BOSHException("Session explicitly closed by caller")); ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Package-private methods: ++ ++ /** ++ * Get the current CM session params. ++ * ++ * @return current session params, or {@code null} ++ */ ++ CMSessionParams getCMSessionParams() { ++ lock.lock(); ++ try { ++ return cmParams; ++ } finally { ++ lock.unlock(); ++ } ++ } ++ ++ /** ++ * Wait until no more messages are waiting to be processed. ++ */ ++ void drain() { ++ lock.lock(); ++ try { ++ LOG.finest("Waiting while draining..."); ++ while (isWorking() ++ && (emptyRequestFuture == null ++ || emptyRequestFuture.isDone())) { ++ try { ++ drained.await(); ++ } catch (InterruptedException intx) { ++ LOG.log(Level.FINEST, INTERRUPTED, intx); ++ } ++ } ++ LOG.finest("Drained"); ++ } finally { ++ lock.unlock(); ++ } ++ } ++ ++ /** ++ * Test method used to forcibly discard next exchange. ++ * ++ * @param interceptor exchange interceptor ++ */ ++ void setExchangeInterceptor(final ExchangeInterceptor interceptor) { ++ exchInterceptor.set(interceptor); ++ } ++ ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Private methods: ++ ++ /** ++ * Initialize the session. This initializes the underlying HTTP ++ * transport implementation and starts the receive thread. ++ */ ++ private void init() { ++ assertUnlocked(); ++ ++ lock.lock(); ++ try { ++ httpSender.init(cfg); ++ procThread = new Thread(procRunnable); ++ procThread.setDaemon(true); ++ procThread.setName(BOSHClient.class.getSimpleName() ++ + "[" + System.identityHashCode(this) ++ + "]: Receive thread"); ++ procThread.start(); ++ } finally { ++ lock.unlock(); ++ } ++ } ++ ++ /** ++ * Destroy this session. ++ * ++ * @param cause the reason for the session termination, or {@code null} ++ * for normal termination ++ */ ++ private void dispose(final Throwable cause) { ++ assertUnlocked(); ++ ++ lock.lock(); ++ try { ++ if (procThread == null) { ++ // Already disposed ++ return; ++ } ++ procThread = null; ++ } finally { ++ lock.unlock(); ++ } ++ ++ if (cause == null) { ++ fireConnectionClosed(); ++ } else { ++ fireConnectionClosedOnError(cause); ++ } ++ ++ lock.lock(); ++ try { ++ clearEmptyRequest(); ++ exchanges = null; ++ cmParams = null; ++ pendingResponseAcks = null; ++ pendingRequestAcks = null; ++ notEmpty.signalAll(); ++ notFull.signalAll(); ++ drained.signalAll(); ++ } finally { ++ lock.unlock(); ++ } ++ ++ httpSender.destroy(); ++ schedExec.shutdownNow(); ++ } ++ ++ /** ++ * Determines if the message body specified indicates a request to ++ * pause the session. ++ * ++ * @param msg message to evaluate ++ * @return {@code true} if the message is a pause request, {@code false} ++ * otherwise ++ */ ++ private static boolean isPause(final AbstractBody msg) { ++ return msg.getAttribute(Attributes.PAUSE) != null; ++ } ++ ++ /** ++ * Determines if the message body specified indicates a termination of ++ * the session. ++ * ++ * @param msg message to evaluate ++ * @return {@code true} if the message is a session termination, ++ * {@code false} otherwise ++ */ ++ private static boolean isTermination(final AbstractBody msg) { ++ return TERMINATE.equals(msg.getAttribute(Attributes.TYPE)); ++ } ++ ++ /** ++ * Evaluates the HTTP response code and response message and returns the ++ * terminal binding condition that it describes, if any. ++ * ++ * @param respCode HTTP response code ++ * @param respBody response body ++ * @return terminal binding condition, or {@code null} if not a terminal ++ * binding condition message ++ */ ++ private TerminalBindingCondition getTerminalBindingCondition( ++ final int respCode, ++ final AbstractBody respBody) { ++ assertLocked(); ++ ++ if (isTermination(respBody)) { ++ String str = respBody.getAttribute(Attributes.CONDITION); ++ return TerminalBindingCondition.forString(str); ++ } ++ // Check for deprecated HTTP Error Conditions ++ if (cmParams != null && cmParams.getVersion() == null) { ++ return TerminalBindingCondition.forHTTPResponseCode(respCode); ++ } ++ return null; ++ } ++ ++ /** ++ * Determines if the message specified is immediately sendable or if it ++ * needs to block until the session state changes. ++ * ++ * @param msg message to evaluate ++ * @return {@code true} if the message can be immediately sent, ++ * {@code false} otherwise ++ */ ++ private boolean isImmediatelySendable(final AbstractBody msg) { ++ assertLocked(); ++ ++ if (cmParams == null) { ++ // block if we're waiting for a response to our first request ++ return exchanges.isEmpty(); ++ } ++ ++ AttrRequests requests = cmParams.getRequests(); ++ if (requests == null) { ++ return true; ++ } ++ int maxRequests = requests.intValue(); ++ if (exchanges.size() < maxRequests) { ++ return true; ++ } ++ if (exchanges.size() == maxRequests ++ && (isTermination(msg) || isPause(msg))) { ++ // One additional terminate or pause message is allowed ++ return true; ++ } ++ return false; ++ } ++ ++ /** ++ * Determines whether or not the session is still active. ++ * ++ * @return {@code true} if it is, {@code false} otherwise ++ */ ++ private boolean isWorking() { ++ assertLocked(); ++ ++ return procThread != null; ++ } ++ ++ /** ++ * Blocks until either the message provided becomes immediately ++ * sendable or until the session is terminated. ++ * ++ * @param msg message to evaluate ++ */ ++ private void blockUntilSendable(final AbstractBody msg) { ++ assertLocked(); ++ ++ while (isWorking() && !isImmediatelySendable(msg)) { ++ try { ++ notFull.await(); ++ } catch (InterruptedException intx) { ++ LOG.log(Level.FINEST, INTERRUPTED, intx); ++ } ++ } ++ } ++ ++ /** ++ * Modifies the specified body message such that it becomes a new ++ * BOSH session creation request. ++ * ++ * @param rid request ID to use ++ * @param orig original body to modify ++ * @return modified message which acts as a session creation request ++ */ ++ private ComposableBody applySessionCreationRequest( ++ final long rid, final ComposableBody orig) throws BOSHException { ++ assertLocked(); ++ ++ Builder builder = orig.rebuild(); ++ builder.setAttribute(Attributes.TO, cfg.getTo()); ++ builder.setAttribute(Attributes.XML_LANG, cfg.getLang()); ++ builder.setAttribute(Attributes.VER, ++ AttrVersion.getSupportedVersion().toString()); ++ builder.setAttribute(Attributes.WAIT, "60"); ++ builder.setAttribute(Attributes.HOLD, "1"); ++ builder.setAttribute(Attributes.RID, Long.toString(rid)); ++ applyRoute(builder); ++ applyFrom(builder); ++ builder.setAttribute(Attributes.ACK, "1"); ++ ++ // Make sure the following are NOT present (i.e., during retries) ++ builder.setAttribute(Attributes.SID, null); ++ return builder.build(); ++ } ++ ++ /** ++ * Applies routing information to the request message who's builder has ++ * been provided. ++ * ++ * @param builder builder instance to add routing information to ++ */ ++ private void applyRoute(final Builder builder) { ++ assertLocked(); ++ ++ String route = cfg.getRoute(); ++ if (route != null) { ++ builder.setAttribute(Attributes.ROUTE, route); ++ } ++ } ++ ++ /** ++ * Applies the local station ID information to the request message who's ++ * builder has been provided. ++ * ++ * @param builder builder instance to add station ID information to ++ */ ++ private void applyFrom(final Builder builder) { ++ assertLocked(); ++ ++ String from = cfg.getFrom(); ++ if (from != null) { ++ builder.setAttribute(Attributes.FROM, from); ++ } ++ } ++ ++ /** ++ * Applies existing session data to the outbound request, returning the ++ * modified request. ++ * ++ * This method assumes the lock is currently held. ++ * ++ * @param rid request ID to use ++ * @param orig original/raw request ++ * @return modified request with session information applied ++ */ ++ private ComposableBody applySessionData( ++ final long rid, ++ final ComposableBody orig) throws BOSHException { ++ assertLocked(); ++ ++ Builder builder = orig.rebuild(); ++ builder.setAttribute(Attributes.SID, ++ cmParams.getSessionID().toString()); ++ builder.setAttribute(Attributes.RID, Long.toString(rid)); ++ applyResponseAcknowledgement(builder, rid); ++ return builder.build(); ++ } ++ ++ /** ++ * Sets the 'ack' attribute of the request to the value of the highest ++ * 'rid' of a request for which it has already received a response in the ++ * case where it has also received all responses associated with lower ++ * 'rid' values. The only exception is that, after its session creation ++ * request, the client SHOULD NOT include an 'ack' attribute in any request ++ * if it has received responses to all its previous requests. ++ * ++ * @param builder message builder ++ * @param rid current request RID ++ */ ++ private void applyResponseAcknowledgement( ++ final Builder builder, ++ final long rid) { ++ assertLocked(); ++ ++ if (responseAck.equals(Long.valueOf(-1L))) { ++ // We have not received any responses yet ++ return; ++ } ++ ++ Long prevRID = Long.valueOf(rid - 1L); ++ if (responseAck.equals(prevRID)) { ++ // Implicit ack ++ return; ++ } ++ ++ builder.setAttribute(Attributes.ACK, responseAck.toString()); ++ } ++ ++ /** ++ * While we are "connected", process received responses. ++ * ++ * This method is run in the processing thread. ++ */ ++ private void processMessages() { ++ LOG.log(Level.FINEST, "Processing thread starting"); ++ try { ++ HTTPExchange exch; ++ do { ++ exch = nextExchange(); ++ if (exch == null) { ++ break; ++ } ++ ++ // Test hook to manipulate what the client sees: ++ ExchangeInterceptor interceptor = exchInterceptor.get(); ++ if (interceptor != null) { ++ HTTPExchange newExch = interceptor.interceptExchange(exch); ++ if (newExch == null) { ++ LOG.log(Level.FINE, "Discarding exchange on request " ++ + "of test hook: RID=" ++ + exch.getRequest().getAttribute( ++ Attributes.RID)); ++ lock.lock(); ++ try { ++ exchanges.remove(exch); ++ } finally { ++ lock.unlock(); ++ } ++ continue; ++ } ++ exch = newExch; ++ } ++ ++ processExchange(exch); ++ } while (true); ++ } finally { ++ LOG.log(Level.FINEST, "Processing thread exiting"); ++ } ++ ++ } ++ ++ /** ++ * Get the next message exchange to process, blocking until one becomes ++ * available if nothing is already waiting for processing. ++ * ++ * @return next available exchange to process, or {@code null} if no ++ * exchanges are immediately available ++ */ ++ private HTTPExchange nextExchange() { ++ assertUnlocked(); ++ ++ final Thread thread = Thread.currentThread(); ++ HTTPExchange exch = null; ++ lock.lock(); ++ try { ++ do { ++ if (!thread.equals(procThread)) { ++ break; ++ } ++ exch = exchanges.peek(); ++ if (exch == null) { ++ try { ++ notEmpty.await(); ++ } catch (InterruptedException intx) { ++ LOG.log(Level.FINEST, INTERRUPTED, intx); ++ } ++ } ++ } while (exch == null); ++ } finally { ++ lock.unlock(); ++ } ++ return exch; ++ } ++ ++ /** ++ * Process the next, provided exchange. This is the main processing ++ * method of the receive thread. ++ * ++ * @param exch message exchange to process ++ */ ++ private void processExchange(final HTTPExchange exch) { ++ assertUnlocked(); ++ ++ HTTPResponse resp; ++ AbstractBody body; ++ int respCode; ++ try { ++ resp = exch.getHTTPResponse(); ++ body = resp.getBody(); ++ respCode = resp.getHTTPStatus(); ++ } catch (BOSHException boshx) { ++ LOG.log(Level.FINEST, "Could not obtain response", boshx); ++ dispose(boshx); ++ return; ++ } catch (InterruptedException intx) { ++ LOG.log(Level.FINEST, INTERRUPTED, intx); ++ dispose(intx); ++ return; ++ } ++ fireResponseReceived(body); ++ ++ // Process the message with the current session state ++ AbstractBody req = exch.getRequest(); ++ CMSessionParams params; ++ List toResend = null; ++ lock.lock(); ++ try { ++ // Check for session creation response info, if needed ++ if (cmParams == null) { ++ cmParams = CMSessionParams.fromSessionInit(req, body); ++ ++ // The following call handles the lock. It's not an escape. ++ fireConnectionEstablished(); ++ } ++ params = cmParams; ++ ++ checkForTerminalBindingConditions(body, respCode); ++ if (isTermination(body)) { ++ // Explicit termination ++ lock.unlock(); ++ dispose(null); ++ return; ++ } ++ ++ if (isRecoverableBindingCondition(body)) { ++ // Retransmit outstanding requests ++ if (toResend == null) { ++ toResend = new ArrayList(exchanges.size()); ++ } ++ for (HTTPExchange exchange : exchanges) { ++ HTTPExchange resendExch = ++ new HTTPExchange(exchange.getRequest()); ++ toResend.add(resendExch); ++ } ++ for (HTTPExchange exchange : toResend) { ++ exchanges.add(exchange); ++ } ++ } else { ++ // Process message as normal ++ processRequestAcknowledgements(req, body); ++ processResponseAcknowledgementData(req); ++ HTTPExchange resendExch = ++ processResponseAcknowledgementReport(body); ++ if (resendExch != null && toResend == null) { ++ toResend = new ArrayList(1); ++ toResend.add(resendExch); ++ exchanges.add(resendExch); ++ } ++ } ++ } catch (BOSHException boshx) { ++ LOG.log(Level.FINEST, "Could not process response", boshx); ++ lock.unlock(); ++ dispose(boshx); ++ return; ++ } finally { ++ if (lock.isHeldByCurrentThread()) { ++ try { ++ exchanges.remove(exch); ++ if (exchanges.isEmpty()) { ++ scheduleEmptyRequest(processPauseRequest(req)); ++ } ++ notFull.signalAll(); ++ } finally { ++ lock.unlock(); ++ } ++ } ++ } ++ ++ if (toResend != null) { ++ for (HTTPExchange resend : toResend) { ++ HTTPResponse response = ++ httpSender.send(params, resend.getRequest()); ++ resend.setHTTPResponse(response); ++ fireRequestSent(resend.getRequest()); ++ } ++ } ++ } ++ ++ /** ++ * Clears any scheduled empty requests. ++ */ ++ private void clearEmptyRequest() { ++ assertLocked(); ++ ++ if (emptyRequestFuture != null) { ++ emptyRequestFuture.cancel(false); ++ emptyRequestFuture = null; ++ } ++ } ++ ++ /** ++ * Calculates the default empty request delay/interval to use for the ++ * active session. ++ * ++ * @return delay in milliseconds ++ */ ++ private long getDefaultEmptyRequestDelay() { ++ assertLocked(); ++ ++ // Figure out how long we should wait before sending an empty request ++ AttrPolling polling = cmParams.getPollingInterval(); ++ long delay; ++ if (polling == null) { ++ delay = EMPTY_REQUEST_DELAY; ++ } else { ++ delay = polling.getInMilliseconds(); ++ } ++ return delay; ++ } ++ ++ /** ++ * Schedule an empty request to be sent if no other requests are ++ * sent in a reasonable amount of time. ++ */ ++ private void scheduleEmptyRequest(long delay) { ++ assertLocked(); ++ if (delay < 0L) { ++ throw(new IllegalArgumentException( ++ "Empty request delay must be >= 0 (was: " + delay + ")")); ++ } ++ ++ clearEmptyRequest(); ++ if (!isWorking()) { ++ return; ++ } ++ ++ // Schedule the transmission ++ if (LOG.isLoggable(Level.FINER)) { ++ LOG.finer("Scheduling empty request in " + delay + "ms"); ++ } ++ try { ++ emptyRequestFuture = schedExec.schedule(emptyRequestRunnable, ++ delay, TimeUnit.MILLISECONDS); ++ } catch (RejectedExecutionException rex) { ++ LOG.log(Level.FINEST, "Could not schedule empty request", rex); ++ } ++ drained.signalAll(); ++ } ++ ++ /** ++ * Sends an empty request to maintain session requirements. If a request ++ * is sent within a reasonable time window, the empty request transmission ++ * will be cancelled. ++ */ ++ private void sendEmptyRequest() { ++ assertUnlocked(); ++ // Send an empty request ++ LOG.finest("Sending empty request"); ++ try { ++ send(ComposableBody.builder().build()); ++ } catch (BOSHException boshx) { ++ dispose(boshx); ++ } ++ } ++ ++ /** ++ * Assert that the internal lock is held. ++ */ ++ private void assertLocked() { ++ if (ASSERTIONS) { ++ if (!lock.isHeldByCurrentThread()) { ++ throw(new AssertionError("Lock is not held by current thread")); ++ } ++ return; ++ } ++ } ++ ++ /** ++ * Assert that the internal lock is *not* held. ++ */ ++ private void assertUnlocked() { ++ if (ASSERTIONS) { ++ if (lock.isHeldByCurrentThread()) { ++ throw(new AssertionError("Lock is held by current thread")); ++ } ++ return; ++ } ++ } ++ ++ /** ++ * Checks to see if the response indicates a terminal binding condition ++ * (as per XEP-0124 section 17). If it does, an exception is thrown. ++ * ++ * @param body response body to evaluate ++ * @param code HTTP response code ++ * @throws BOSHException if a terminal binding condition is detected ++ */ ++ private void checkForTerminalBindingConditions( ++ final AbstractBody body, ++ final int code) ++ throws BOSHException { ++ TerminalBindingCondition cond = ++ getTerminalBindingCondition(code, body); ++ if (cond != null) { ++ throw(new BOSHException( ++ "Terminal binding condition encountered: " ++ + cond.getCondition() + " (" ++ + cond.getMessage() + ")")); ++ } ++ } ++ ++ /** ++ * Determines whether or not the response indicates a recoverable ++ * binding condition (as per XEP-0124 section 17). ++ * ++ * @param resp response body ++ * @return {@code true} if it does, {@code false} otherwise ++ */ ++ private static boolean isRecoverableBindingCondition( ++ final AbstractBody resp) { ++ return ERROR.equals(resp.getAttribute(Attributes.TYPE)); ++ } ++ ++ /** ++ * Process the request to determine if the empty request delay ++ * can be determined by looking to see if the request is a pause ++ * request. If it can, the request's delay is returned, otherwise ++ * the default delay is returned. ++ * ++ * @return delay in milliseconds that should elapse prior to an ++ * empty message being sent ++ */ ++ private long processPauseRequest( ++ final AbstractBody req) { ++ assertLocked(); ++ ++ if (cmParams != null && cmParams.getMaxPause() != null) { ++ try { ++ AttrPause pause = AttrPause.createFromString( ++ req.getAttribute(Attributes.PAUSE)); ++ if (pause != null) { ++ long delay = pause.getInMilliseconds() - PAUSE_MARGIN; ++ if (delay < 0) { ++ delay = EMPTY_REQUEST_DELAY; ++ } ++ return delay; ++ } ++ } catch (BOSHException boshx) { ++ LOG.log(Level.FINEST, "Could not extract", boshx); ++ } ++ } ++ ++ return getDefaultEmptyRequestDelay(); ++ } ++ ++ /** ++ * Check the response for request acknowledgements and take appropriate ++ * action. ++ * ++ * This method assumes the lock is currently held. ++ * ++ * @param req request ++ * @param resp response ++ */ ++ private void processRequestAcknowledgements( ++ final AbstractBody req, final AbstractBody resp) { ++ assertLocked(); ++ ++ if (!cmParams.isAckingRequests()) { ++ return; ++ } ++ ++ // If a report or time attribute is set, we aren't acking anything ++ if (resp.getAttribute(Attributes.REPORT) != null) { ++ return; ++ } ++ ++ // Figure out what the highest acked RID is ++ String acked = resp.getAttribute(Attributes.ACK); ++ Long ackUpTo; ++ if (acked == null) { ++ // Implicit ack of all prior requests up until RID ++ ackUpTo = Long.parseLong(req.getAttribute(Attributes.RID)); ++ } else { ++ ackUpTo = Long.parseLong(acked); ++ } ++ ++ // Remove the acked requests from the list ++ if (LOG.isLoggable(Level.FINEST)) { ++ LOG.finest("Removing pending acks up to: " + ackUpTo); ++ } ++ Iterator iter = pendingRequestAcks.iterator(); ++ while (iter.hasNext()) { ++ AbstractBody pending = iter.next(); ++ Long pendingRID = Long.parseLong( ++ pending.getAttribute(Attributes.RID)); ++ if (pendingRID.compareTo(ackUpTo) <= 0) { ++ iter.remove(); ++ } ++ } ++ } ++ ++ /** ++ * Process the response in order to update the response acknowlegement ++ * data. ++ * ++ * This method assumes the lock is currently held. ++ * ++ * @param req request ++ */ ++ private void processResponseAcknowledgementData( ++ final AbstractBody req) { ++ assertLocked(); ++ ++ Long rid = Long.parseLong(req.getAttribute(Attributes.RID)); ++ if (responseAck.equals(Long.valueOf(-1L))) { ++ // This is the first request ++ responseAck = rid; ++ } else { ++ pendingResponseAcks.add(rid); ++ // Remove up until the first missing response (or end of queue) ++ Long whileVal = responseAck; ++ while (whileVal.equals(pendingResponseAcks.first())) { ++ responseAck = whileVal; ++ pendingResponseAcks.remove(whileVal); ++ whileVal = Long.valueOf(whileVal.longValue() + 1); ++ } ++ } ++ } ++ ++ /** ++ * Process the response in order to check for and respond to any potential ++ * ack reports. ++ * ++ * This method assumes the lock is currently held. ++ * ++ * @param resp response ++ * @return exchange to transmit if a resend is to be performed, or ++ * {@code null} if no resend is necessary ++ * @throws BOSHException when a a retry is needed but cannot be performed ++ */ ++ private HTTPExchange processResponseAcknowledgementReport( ++ final AbstractBody resp) ++ throws BOSHException { ++ assertLocked(); ++ ++ String reportStr = resp.getAttribute(Attributes.REPORT); ++ if (reportStr == null) { ++ // No report on this message ++ return null; ++ } ++ ++ Long report = Long.parseLong(reportStr); ++ Long time = Long.parseLong(resp.getAttribute(Attributes.TIME)); ++ if (LOG.isLoggable(Level.FINE)) { ++ LOG.fine("Received report of missing request (RID=" ++ + report + ", time=" + time + "ms)"); ++ } ++ ++ // Find the missing request ++ Iterator iter = pendingRequestAcks.iterator(); ++ AbstractBody req = null; ++ while (iter.hasNext() && req == null) { ++ AbstractBody pending = iter.next(); ++ Long pendingRID = Long.parseLong( ++ pending.getAttribute(Attributes.RID)); ++ if (report.equals(pendingRID)) { ++ req = pending; ++ } ++ } ++ ++ if (req == null) { ++ throw(new BOSHException("Report of missing message with RID '" ++ + reportStr ++ + "' but local copy of that request was not found")); ++ } ++ ++ // Resend the missing request ++ HTTPExchange exch = new HTTPExchange(req); ++ exchanges.add(exch); ++ notEmpty.signalAll(); ++ return exch; ++ } ++ ++ /** ++ * Notifies all request listeners that the specified request is being ++ * sent. ++ * ++ * @param request request being sent ++ */ ++ private void fireRequestSent(final AbstractBody request) { ++ assertUnlocked(); ++ ++ BOSHMessageEvent event = null; ++ for (BOSHClientRequestListener listener : requestListeners) { ++ if (event == null) { ++ event = BOSHMessageEvent.createRequestSentEvent(this, request); ++ } ++ try { ++ listener.requestSent(event); ++ } catch (Exception ex) { ++ LOG.log(Level.WARNING, UNHANDLED, ex); ++ } ++ } ++ } ++ ++ /** ++ * Notifies all response listeners that the specified response has been ++ * received. ++ * ++ * @param response response received ++ */ ++ private void fireResponseReceived(final AbstractBody response) { ++ assertUnlocked(); ++ ++ BOSHMessageEvent event = null; ++ for (BOSHClientResponseListener listener : responseListeners) { ++ if (event == null) { ++ event = BOSHMessageEvent.createResponseReceivedEvent( ++ this, response); ++ } ++ try { ++ listener.responseReceived(event); ++ } catch (Exception ex) { ++ LOG.log(Level.WARNING, UNHANDLED, ex); ++ } ++ } ++ } ++ ++ /** ++ * Notifies all connection listeners that the session has been successfully ++ * established. ++ */ ++ private void fireConnectionEstablished() { ++ final boolean hadLock = lock.isHeldByCurrentThread(); ++ if (hadLock) { ++ lock.unlock(); ++ } ++ try { ++ BOSHClientConnEvent event = null; ++ for (BOSHClientConnListener listener : connListeners) { ++ if (event == null) { ++ event = BOSHClientConnEvent ++ .createConnectionEstablishedEvent(this); ++ } ++ try { ++ listener.connectionEvent(event); ++ } catch (Exception ex) { ++ LOG.log(Level.WARNING, UNHANDLED, ex); ++ } ++ } ++ } finally { ++ if (hadLock) { ++ lock.lock(); ++ } ++ } ++ } ++ ++ /** ++ * Notifies all connection listeners that the session has been ++ * terminated normally. ++ */ ++ private void fireConnectionClosed() { ++ assertUnlocked(); ++ ++ BOSHClientConnEvent event = null; ++ for (BOSHClientConnListener listener : connListeners) { ++ if (event == null) { ++ event = BOSHClientConnEvent.createConnectionClosedEvent(this); ++ } ++ try { ++ listener.connectionEvent(event); ++ } catch (Exception ex) { ++ LOG.log(Level.WARNING, UNHANDLED, ex); ++ } ++ } ++ } ++ ++ /** ++ * Notifies all connection listeners that the session has been ++ * terminated due to the exceptional condition provided. ++ * ++ * @param cause cause of the termination ++ */ ++ private void fireConnectionClosedOnError( ++ final Throwable cause) { ++ assertUnlocked(); ++ ++ BOSHClientConnEvent event = null; ++ for (BOSHClientConnListener listener : connListeners) { ++ if (event == null) { ++ event = BOSHClientConnEvent ++ .createConnectionClosedOnErrorEvent( ++ this, pendingRequestAcks, cause); ++ } ++ try { ++ listener.connectionEvent(event); ++ } catch (Exception ex) { ++ LOG.log(Level.WARNING, UNHANDLED, ex); ++ } ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientConfig.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientConfig.java +new file mode 100644 +index 0000000..23915b6 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientConfig.java +@@ -0,0 +1,446 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.net.URI; ++import javax.net.ssl.SSLContext; ++ ++/** ++ * BOSH client configuration information. Instances of this class contain ++ * all information necessary to establish connectivity with a remote ++ * connection manager. ++ *

++ * Instances of this class are immutable, thread-safe, ++ * and can be re-used to configure multiple client session instances. ++ */ ++public final class BOSHClientConfig { ++ ++ /** ++ * Connection manager URI. ++ */ ++ private final URI uri; ++ ++ /** ++ * Target domain. ++ */ ++ private final String to; ++ ++ /** ++ * Client ID of this station. ++ */ ++ private final String from; ++ ++ /** ++ * Default XML language. ++ */ ++ private final String lang; ++ ++ /** ++ * Routing information for messages sent to CM. ++ */ ++ private final String route; ++ ++ /** ++ * Proxy host. ++ */ ++ private final String proxyHost; ++ ++ /** ++ * Proxy port. ++ */ ++ private final int proxyPort; ++ ++ /** ++ * SSL context. ++ */ ++ private final SSLContext sslContext; ++ ++ /** ++ * Flag indicating that compression should be attempted, if possible. ++ */ ++ private final boolean compressionEnabled; ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Classes: ++ ++ /** ++ * Class instance builder, after the builder pattern. This allows each ++ * {@code BOSHClientConfig} instance to be immutable while providing ++ * flexibility when building new {@code BOSHClientConfig} instances. ++ *

++ * Instances of this class are not thread-safe. If template-style ++ * use is desired, see the {@code create(BOSHClientConfig)} method. ++ */ ++ public static final class Builder { ++ // Required args ++ private final URI bURI; ++ private final String bDomain; ++ ++ // Optional args ++ private String bFrom; ++ private String bLang; ++ private String bRoute; ++ private String bProxyHost; ++ private int bProxyPort; ++ private SSLContext bSSLContext; ++ private Boolean bCompression; ++ ++ /** ++ * Creates a new builder instance, used to create instances of the ++ * {@code BOSHClientConfig} class. ++ * ++ * @param cmURI URI to use to contact the connection manager ++ * @param domain target domain to communicate with ++ */ ++ private Builder(final URI cmURI, final String domain) { ++ bURI = cmURI; ++ bDomain = domain; ++ } ++ ++ /** ++ * Creates a new builder instance, used to create instances of the ++ * {@code BOSHClientConfig} class. ++ * ++ * @param cmURI URI to use to contact the connection manager ++ * @param domain target domain to communicate with ++ * @return builder instance ++ */ ++ public static Builder create(final URI cmURI, final String domain) { ++ if (cmURI == null) { ++ throw(new IllegalArgumentException( ++ "Connection manager URI must not be null")); ++ } ++ if (domain == null) { ++ throw(new IllegalArgumentException( ++ "Target domain must not be null")); ++ } ++ String scheme = cmURI.getScheme(); ++ if (!("http".equals(scheme) || "https".equals(scheme))) { ++ throw(new IllegalArgumentException( ++ "Only 'http' and 'https' URI are allowed")); ++ } ++ return new Builder(cmURI, domain); ++ } ++ ++ /** ++ * Creates a new builder instance using the existing configuration ++ * provided as a starting point. ++ * ++ * @param cfg configuration to copy ++ * @return builder instance ++ */ ++ public static Builder create(final BOSHClientConfig cfg) { ++ Builder result = new Builder(cfg.getURI(), cfg.getTo()); ++ result.bFrom = cfg.getFrom(); ++ result.bLang = cfg.getLang(); ++ result.bRoute = cfg.getRoute(); ++ result.bProxyHost = cfg.getProxyHost(); ++ result.bProxyPort = cfg.getProxyPort(); ++ result.bSSLContext = cfg.getSSLContext(); ++ result.bCompression = cfg.isCompressionEnabled(); ++ return result; ++ } ++ ++ /** ++ * Set the ID of the client station, to be forwarded to the connection ++ * manager when new sessions are created. ++ * ++ * @param id client ID ++ * @return builder instance ++ */ ++ public Builder setFrom(final String id) { ++ if (id == null) { ++ throw(new IllegalArgumentException( ++ "Client ID must not be null")); ++ } ++ bFrom = id; ++ return this; ++ } ++ ++ /** ++ * Set the default language of any human-readable content within the ++ * XML. ++ * ++ * @param lang XML language ID ++ * @return builder instance ++ */ ++ public Builder setXMLLang(final String lang) { ++ if (lang == null) { ++ throw(new IllegalArgumentException( ++ "Default language ID must not be null")); ++ } ++ bLang = lang; ++ return this; ++ } ++ ++ /** ++ * Sets the destination server/domain that the client should connect to. ++ * Connection managers may be configured to enable sessions with more ++ * that one server in different domains. When requesting a session with ++ * such a "proxy" connection manager, a client should use this method to ++ * specify the server with which it wants to communicate. ++ * ++ * @param protocol connection protocol (e.g, "xmpp") ++ * @param host host or domain to be served by the remote server. Note ++ * that this is not necessarily the host name or domain name of the ++ * remote server. ++ * @param port port number of the remote server ++ * @return builder instance ++ */ ++ public Builder setRoute( ++ final String protocol, ++ final String host, ++ final int port) { ++ if (protocol == null) { ++ throw(new IllegalArgumentException("Protocol cannot be null")); ++ } ++ if (protocol.contains(":")) { ++ throw(new IllegalArgumentException( ++ "Protocol cannot contain the ':' character")); ++ } ++ if (host == null) { ++ throw(new IllegalArgumentException("Host cannot be null")); ++ } ++ if (host.contains(":")) { ++ throw(new IllegalArgumentException( ++ "Host cannot contain the ':' character")); ++ } ++ if (port <= 0) { ++ throw(new IllegalArgumentException("Port number must be > 0")); ++ } ++ bRoute = protocol + ":" + host + ":" + port; ++ return this; ++ } ++ ++ /** ++ * Specify the hostname and port of an HTTP proxy to connect through. ++ * ++ * @param hostName proxy hostname ++ * @param port proxy port number ++ * @return builder instance ++ */ ++ public Builder setProxy(final String hostName, final int port) { ++ if (hostName == null || hostName.length() == 0) { ++ throw(new IllegalArgumentException( ++ "Proxy host name cannot be null or empty")); ++ } ++ if (port <= 0) { ++ throw(new IllegalArgumentException( ++ "Proxy port must be > 0")); ++ } ++ bProxyHost = hostName; ++ bProxyPort = port; ++ return this; ++ } ++ ++ /** ++ * Set the SSL context to use for this session. This can be used ++ * to configure certificate-based authentication, etc.. ++ * ++ * @param ctx SSL context ++ * @return builder instance ++ */ ++ public Builder setSSLContext(final SSLContext ctx) { ++ if (ctx == null) { ++ throw(new IllegalArgumentException( ++ "SSL context cannot be null")); ++ } ++ bSSLContext = ctx; ++ return this; ++ } ++ ++ /** ++ * Set whether or not compression of the underlying data stream ++ * should be attempted. By default, compression is disabled. ++ * ++ * @param enabled set to {@code true} if compression should be ++ * attempted when possible, {@code false} to disable compression ++ * @return builder instance ++ */ ++ public Builder setCompressionEnabled(final boolean enabled) { ++ bCompression = Boolean.valueOf(enabled); ++ return this; ++ } ++ ++ /** ++ * Build the immutable object instance with the current configuration. ++ * ++ * @return BOSHClientConfig instance ++ */ ++ public BOSHClientConfig build() { ++ // Default XML language ++ String lang; ++ if (bLang == null) { ++ lang = "en"; ++ } else { ++ lang = bLang; ++ } ++ ++ // Default proxy port ++ int port; ++ if (bProxyHost == null) { ++ port = 0; ++ } else { ++ port = bProxyPort; ++ } ++ ++ // Default compression ++ boolean compression; ++ if (bCompression == null) { ++ compression = false; ++ } else { ++ compression = bCompression.booleanValue(); ++ } ++ ++ return new BOSHClientConfig( ++ bURI, ++ bDomain, ++ bFrom, ++ lang, ++ bRoute, ++ bProxyHost, ++ port, ++ bSSLContext, ++ compression); ++ } ++ ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Constructor: ++ ++ /** ++ * Prevent direct construction. ++ * ++ * @param cURI URI of the connection manager to connect to ++ * @param cDomain the target domain of the first stream ++ * @param cFrom client ID ++ * @param cLang default XML language ++ * @param cRoute target route ++ * @param cProxyHost proxy host ++ * @param cProxyPort proxy port ++ * @param cSSLContext SSL context ++ * @param cCompression compression enabled flag ++ */ ++ private BOSHClientConfig( ++ final URI cURI, ++ final String cDomain, ++ final String cFrom, ++ final String cLang, ++ final String cRoute, ++ final String cProxyHost, ++ final int cProxyPort, ++ final SSLContext cSSLContext, ++ final boolean cCompression) { ++ uri = cURI; ++ to = cDomain; ++ from = cFrom; ++ lang = cLang; ++ route = cRoute; ++ proxyHost = cProxyHost; ++ proxyPort = cProxyPort; ++ sslContext = cSSLContext; ++ compressionEnabled = cCompression; ++ } ++ ++ /** ++ * Get the URI to use to contact the connection manager. ++ * ++ * @return connection manager URI. ++ */ ++ public URI getURI() { ++ return uri; ++ } ++ ++ /** ++ * Get the ID of the target domain. ++ * ++ * @return domain id ++ */ ++ public String getTo() { ++ return to; ++ } ++ ++ /** ++ * Get the ID of the local client. ++ * ++ * @return client id, or {@code null} ++ */ ++ public String getFrom() { ++ return from; ++ } ++ ++ /** ++ * Get the default language of any human-readable content within the ++ * XML. Defaults to "en". ++ * ++ * @return XML language ID ++ */ ++ public String getLang() { ++ return lang; ++ } ++ ++ /** ++ * Get the routing information for messages sent to the CM. ++ * ++ * @return route attribute string, or {@code null} if no routing ++ * info was provided. ++ */ ++ public String getRoute() { ++ return route; ++ } ++ ++ /** ++ * Get the HTTP proxy host to use. ++ * ++ * @return proxy host, or {@code null} if no proxy information was specified ++ */ ++ public String getProxyHost() { ++ return proxyHost; ++ } ++ ++ /** ++ * Get the HTTP proxy port to use. ++ * ++ * @return proxy port, or 0 if no proxy information was specified ++ */ ++ public int getProxyPort() { ++ return proxyPort; ++ } ++ ++ /** ++ * Get the SSL context to use for this session. ++ * ++ * @return SSL context instance to use, or {@code null} if no ++ * context instance was provided. ++ */ ++ public SSLContext getSSLContext() { ++ return sslContext; ++ } ++ ++ /** ++ * Determines whether or not compression of the underlying data stream ++ * should be attempted/allowed. Defaults to {@code false}. ++ * ++ * @return {@code true} if compression should be attempted, {@code false} ++ * if compression is disabled or was not specified ++ */ ++ boolean isCompressionEnabled() { ++ return compressionEnabled; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientConnEvent.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientConnEvent.java +new file mode 100644 +index 0000000..0ac7943 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientConnEvent.java +@@ -0,0 +1,189 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.EventObject; ++import java.util.List; ++ ++/** ++ * Client connection event, notifying of changes in connection state. ++ *

++ * This class is immutable and thread-safe. ++ */ ++public final class BOSHClientConnEvent extends EventObject { ++ ++ /** ++ * Serialized version. ++ */ ++ private static final long serialVersionUID = 1L; ++ ++ /** ++ * Boolean flag indicating whether or not a session has been established ++ * and is currently active. ++ */ ++ private final boolean connected; ++ ++ /** ++ * List of outstanding requests which may not have been sent and/or ++ * acknowledged by the remote CM. ++ */ ++ private final List requests; ++ ++ /** ++ * Cause of the session termination, or {@code null}. ++ */ ++ private final Throwable cause; ++ ++ /** ++ * Creates a new connection event instance. ++ * ++ * @param source event source ++ * @param cConnected flag indicating whether or not the session is ++ * currently active ++ * @param cRequests outstanding requests when an error condition is ++ * detected, or {@code null} when not an error condition ++ * @param cCause cause of the error condition, or {@code null} when no ++ * error condition is present ++ */ ++ private BOSHClientConnEvent( ++ final BOSHClient source, ++ final boolean cConnected, ++ final List cRequests, ++ final Throwable cCause) { ++ super(source); ++ connected = cConnected; ++ cause = cCause; ++ ++ if (connected) { ++ if (cCause != null) { ++ throw(new IllegalStateException( ++ "Cannot be connected and have a cause")); ++ } ++ if (cRequests != null && cRequests.size() > 0) { ++ throw(new IllegalStateException( ++ "Cannot be connected and have outstanding requests")); ++ } ++ } ++ ++ if (cRequests == null) { ++ requests = Collections.emptyList(); ++ } else { ++ // Defensive copy: ++ requests = Collections.unmodifiableList( ++ new ArrayList(cRequests)); ++ } ++ } ++ ++ /** ++ * Creates a new connection establishment event. ++ * ++ * @param source client which has become connected ++ * @return event instance ++ */ ++ static BOSHClientConnEvent createConnectionEstablishedEvent( ++ final BOSHClient source) { ++ return new BOSHClientConnEvent(source, true, null, null); ++ } ++ ++ /** ++ * Creates a new successful connection closed event. This represents ++ * a clean termination of the client session. ++ * ++ * @param source client which has been disconnected ++ * @return event instance ++ */ ++ static BOSHClientConnEvent createConnectionClosedEvent( ++ final BOSHClient source) { ++ return new BOSHClientConnEvent(source, false, null, null); ++ } ++ ++ /** ++ * Creates a connection closed on error event. This represents ++ * an unexpected termination of the client session. ++ * ++ * @param source client which has been disconnected ++ * @param outstanding list of requests which may not have been received ++ * by the remote connection manager ++ * @param cause cause of termination ++ * @return event instance ++ */ ++ static BOSHClientConnEvent createConnectionClosedOnErrorEvent( ++ final BOSHClient source, ++ final List outstanding, ++ final Throwable cause) { ++ return new BOSHClientConnEvent(source, false, outstanding, cause); ++ } ++ ++ /** ++ * Gets the client from which this event originated. ++ * ++ * @return client instance ++ */ ++ public BOSHClient getBOSHClient() { ++ return (BOSHClient) getSource(); ++ } ++ ++ /** ++ * Returns whether or not the session has been successfully established ++ * and is currently active. ++ * ++ * @return {@code true} if a session is active, {@code false} otherwise ++ */ ++ public boolean isConnected() { ++ return connected; ++ } ++ ++ /** ++ * Returns whether or not this event indicates an error condition. This ++ * will never return {@code true} when {@code isConnected()} returns ++ * {@code true}. ++ * ++ * @return {@code true} if the event indicates a terminal error has ++ * occurred, {@code false} otherwise. ++ */ ++ public boolean isError() { ++ return cause != null; ++ } ++ ++ /** ++ * Returns the underlying cause of the error condition. This method is ++ * guaranteed to return {@code null} when @{code isError()} returns ++ * {@code false}. Similarly, this method is guaranteed to return ++ * non-@{code null} if {@code isError()} returns {@code true}. ++ * ++ * @return underlying cause of the error condition, or {@code null} if ++ * this event does not represent an error condition ++ */ ++ public Throwable getCause() { ++ return cause; ++ } ++ ++ /** ++ * Get the list of requests which may not have been sent or were not ++ * acknowledged by the remote connection manager prior to session ++ * termination. ++ * ++ * @return list of messages which may not have been received by the remote ++ * connection manager, or an empty list if the session is still connected ++ */ ++ public List getOutstandingRequests() { ++ return requests; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientConnListener.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientConnListener.java +new file mode 100644 +index 0000000..6d646cb +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientConnListener.java +@@ -0,0 +1,34 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * Interface used by parties interested in monitoring the connection state ++ * of a client session. ++ */ ++public interface BOSHClientConnListener { ++ ++ /** ++ * Called when the connection state of the client which the listener ++ * is registered against has changed. The event object supplied can ++ * be used to determine the current session state. ++ * ++ * @param connEvent connection event describing the state ++ */ ++ void connectionEvent(BOSHClientConnEvent connEvent); ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientRequestListener.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientRequestListener.java +new file mode 100644 +index 0000000..2cc92f3 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientRequestListener.java +@@ -0,0 +1,45 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * Interface used by parties interested in monitoring outbound requests made ++ * by the client to the connection manager (CM). No opportunity is provided ++ * to manipulate the outbound request. ++ *

++ * The messages being sent are typically modified copies of the message ++ * body provided to the {@code BOSHClient} instance, built from the ++ * originally provided message body plus additional BOSH protocol ++ * state and information. Messages may also be sent automatically when the ++ * protocol requires it, such as maintaining a minimum number of open ++ * connections to the connection manager. ++ *

++ * Listeners are executed by the sending thread immediately prior to ++ * message transmission and should not block for any significant amount ++ * of time. ++ */ ++public interface BOSHClientRequestListener { ++ ++ /** ++ * Called when the listener is being notified that a client request is ++ * about to be sent to the connection manager. ++ * ++ * @param event event instance containing the message being sent ++ */ ++ void requestSent(BOSHMessageEvent event); ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientResponseListener.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientResponseListener.java +new file mode 100644 +index 0000000..1d86e4f +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientResponseListener.java +@@ -0,0 +1,37 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * Interface used by parties interested in monitoring inbound responses ++ * to the client from the connection manager (CM). No opportunity is provided ++ * to manipulate the response. ++ *

++ * Listeners are executed by the message processing thread and should not ++ * block for any significant amount of time. ++ */ ++public interface BOSHClientResponseListener { ++ ++ /** ++ * Called when the listener is being notified that a response has been ++ * received from the connection manager. ++ * ++ * @param event event instance containing the message being sent ++ */ ++ void responseReceived(BOSHMessageEvent event); ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHException.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHException.java +new file mode 100644 +index 0000000..e0bc05b +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHException.java +@@ -0,0 +1,50 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * Exception class used by the BOSH API to minimize the number of checked ++ * exceptions which must be handled by the user of the API. ++ */ ++public class BOSHException extends Exception { ++ ++ /** ++ * Servial version UID. ++ */ ++ private static final long serialVersionUID = 1L; ++ ++ /** ++ * Creates a new exception isntance with the specified descriptive message. ++ * ++ * @param msg description of the exceptional condition ++ */ ++ public BOSHException(final String msg) { ++ super(msg); ++ } ++ ++ /** ++ * Creates a new exception isntance with the specified descriptive ++ * message and the underlying root cause of the exceptional condition. ++ * ++ * @param msg description of the exceptional condition ++ * @param cause root cause or instigator of the condition ++ */ ++ public BOSHException(final String msg, final Throwable cause) { ++ super(msg, cause); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHMessageEvent.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHMessageEvent.java +new file mode 100644 +index 0000000..550903e +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHMessageEvent.java +@@ -0,0 +1,92 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.util.EventObject; ++ ++/** ++ * Event representing a message sent to or from a BOSH connection manager. ++ *

++ * This class is immutable and thread-safe. ++ */ ++public final class BOSHMessageEvent extends EventObject { ++ ++ /** ++ * Serialized version. ++ */ ++ private static final long serialVersionUID = 1L; ++ ++ /** ++ * Message which was sent or received. ++ */ ++ private final AbstractBody body; ++ ++ /** ++ * Creates a new message event instance. ++ * ++ * @param source event source ++ * @param cBody message body ++ */ ++ private BOSHMessageEvent( ++ final Object source, ++ final AbstractBody cBody) { ++ super(source); ++ if (cBody == null) { ++ throw(new IllegalArgumentException( ++ "message body may not be null")); ++ } ++ body = cBody; ++ } ++ ++ /** ++ * Creates a new message event for clients sending events to the ++ * connection manager. ++ * ++ * @param source sender of the message ++ * @param body message body ++ * @return event instance ++ */ ++ static BOSHMessageEvent createRequestSentEvent( ++ final BOSHClient source, ++ final AbstractBody body) { ++ return new BOSHMessageEvent(source, body); ++ } ++ ++ /** ++ * Creates a new message event for clients receiving new messages ++ * from the connection manager. ++ * ++ * @param source receiver of the message ++ * @param body message body ++ * @return event instance ++ */ ++ static BOSHMessageEvent createResponseReceivedEvent( ++ final BOSHClient source, ++ final AbstractBody body) { ++ return new BOSHMessageEvent(source, body); ++ } ++ ++ /** ++ * Gets the message body which was sent or received. ++ * ++ * @return message body ++ */ ++ public AbstractBody getBody() { ++ return body; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParser.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParser.java +new file mode 100644 +index 0000000..5ef5276 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParser.java +@@ -0,0 +1,36 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * Interface for parser implementations to implement in order to abstract the ++ * business of XML parsing out of the Body class. This allows us to leverage ++ * a variety of parser implementations to gain performance advantages. ++ */ ++interface BodyParser { ++ ++ /** ++ * Parses the XML message, extracting the useful data from the initial ++ * body element and returning it in a results object. ++ * ++ * @param xml XML to parse ++ * @return useful data parsed out of the XML ++ * @throws BOSHException on parse error ++ */ ++ BodyParserResults parse(String xml) throws BOSHException; ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParserResults.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParserResults.java +new file mode 100644 +index 0000000..955e4bf +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParserResults.java +@@ -0,0 +1,64 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.util.HashMap; ++import java.util.Map; ++ ++/** ++ * Data extracted from a raw XML message by a BodyParser implementation. ++ * Currently, this is limited to the attributes of the wrapper element. ++ */ ++final class BodyParserResults { ++ ++ /** ++ * Map of qualified names to their values. This map is defined to ++ * match the requirement of the {@code Body} class to prevent ++ * excessive copying. ++ */ ++ private final Map attrs = ++ new HashMap(); ++ ++ /** ++ * Constructor. ++ */ ++ BodyParserResults() { ++ // Empty ++ } ++ ++ /** ++ * Add an attribute definition to the results. ++ * ++ * @param name attribute's qualified name ++ * @param value attribute value ++ */ ++ void addBodyAttributeValue( ++ final BodyQName name, ++ final String value) { ++ attrs.put(name, value); ++ } ++ ++ /** ++ * Returns the map of attributes added by the parser. ++ * ++ * @return map of atributes. Note: This is the live instance, not a copy. ++ */ ++ Map getAttributes() { ++ return attrs; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParserSAX.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParserSAX.java +new file mode 100644 +index 0000000..54c6c01 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParserSAX.java +@@ -0,0 +1,206 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.io.ByteArrayInputStream; ++import java.io.IOException; ++import java.io.InputStream; ++import java.lang.ref.SoftReference; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++import javax.xml.parsers.ParserConfigurationException; ++import javax.xml.parsers.SAXParser; ++import javax.xml.parsers.SAXParserFactory; ++import org.xml.sax.Attributes; ++import org.xml.sax.SAXException; ++import org.xml.sax.helpers.DefaultHandler; ++ ++/** ++ * Implementation of the BodyParser interface which uses the SAX API ++ * that is part of the JDK. Due to the fact that we can cache and reuse ++ * SAXPArser instances, this has proven to be significantly faster than the ++ * use of the javax.xml.stream API introduced in Java 6 while simultaneously ++ * providing an implementation accessible to Java 5 users. ++ */ ++final class BodyParserSAX implements BodyParser { ++ ++ /** ++ * Logger. ++ */ ++ private static final Logger LOG = ++ Logger.getLogger(BodyParserSAX.class.getName()); ++ ++ /** ++ * SAX parser factory. ++ */ ++ private static final SAXParserFactory SAX_FACTORY; ++ static { ++ SAX_FACTORY = SAXParserFactory.newInstance(); ++ SAX_FACTORY.setNamespaceAware(true); ++ SAX_FACTORY.setValidating(false); ++ } ++ ++ /** ++ * Thread local to contain a SAX parser instance for each thread that ++ * attempts to use one. This allows us to gain an order of magnitude of ++ * performance as a result of not constructing parsers for each ++ * invocation while retaining thread safety. ++ */ ++ private static final ThreadLocal> PARSER = ++ new ThreadLocal>() { ++ @Override protected SoftReference initialValue() { ++ return new SoftReference(null); ++ } ++ }; ++ ++ /** ++ * SAX event handler class. ++ */ ++ private static final class Handler extends DefaultHandler { ++ private final BodyParserResults result; ++ private final SAXParser parser; ++ private String defaultNS = null; ++ ++ private Handler(SAXParser theParser, BodyParserResults results) { ++ parser = theParser; ++ result = results; ++ } ++ ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public void startElement( ++ final String uri, ++ final String localName, ++ final String qName, ++ final Attributes attributes) { ++ if (LOG.isLoggable(Level.FINEST)) { ++ LOG.finest("Start element: " + qName); ++ LOG.finest(" URI: " + uri); ++ LOG.finest(" local: " + localName); ++ } ++ ++ BodyQName bodyName = AbstractBody.getBodyQName(); ++ // Make sure the first element is correct ++ if (!(bodyName.getNamespaceURI().equals(uri) ++ && bodyName.getLocalPart().equals(localName))) { ++ throw(new IllegalStateException( ++ "Root element was not '" + bodyName.getLocalPart() ++ + "' in the '" + bodyName.getNamespaceURI() ++ + "' namespace. (Was '" + localName + "' in '" + uri ++ + "')")); ++ } ++ ++ // Read in the attributes, making sure to expand the namespaces ++ // as needed. ++ for (int idx=0; idx < attributes.getLength(); idx++) { ++ String attrURI = attributes.getURI(idx); ++ if (attrURI.length() == 0) { ++ attrURI = defaultNS; ++ } ++ String attrLN = attributes.getLocalName(idx); ++ String attrVal = attributes.getValue(idx); ++ if (LOG.isLoggable(Level.FINEST)) { ++ LOG.finest(" Attribute: {" + attrURI + "}" ++ + attrLN + " = '" + attrVal + "'"); ++ } ++ ++ BodyQName aqn = BodyQName.create(attrURI, attrLN); ++ result.addBodyAttributeValue(aqn, attrVal); ++ } ++ ++ parser.reset(); ++ } ++ ++ /** ++ * {@inheritDoc} ++ * ++ * This implementation uses this event hook to keep track of the ++ * default namespace on the body element. ++ */ ++ @Override ++ public void startPrefixMapping( ++ final String prefix, ++ final String uri) { ++ if (prefix.length() == 0) { ++ if (LOG.isLoggable(Level.FINEST)) { ++ LOG.finest("Prefix mapping: => " + uri); ++ } ++ defaultNS = uri; ++ } else { ++ if (LOG.isLoggable(Level.FINEST)) { ++ LOG.info("Prefix mapping: " + prefix + " => " + uri); ++ } ++ } ++ } ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // BodyParser interface methods: ++ ++ /** ++ * {@inheritDoc} ++ */ ++ public BodyParserResults parse(String xml) throws BOSHException { ++ BodyParserResults result = new BodyParserResults(); ++ Exception thrown; ++ try { ++ InputStream inStream = new ByteArrayInputStream(xml.getBytes()); ++ SAXParser parser = getSAXParser(); ++ parser.parse(inStream, new Handler(parser, result)); ++ return result; ++ } catch (SAXException saxx) { ++ thrown = saxx; ++ } catch (IOException iox) { ++ thrown = iox; ++ } ++ throw(new BOSHException("Could not parse body:\n" + xml, thrown)); ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Private methods: ++ ++ /** ++ * Gets a SAXParser for use in parsing incoming messages. ++ * ++ * @return parser instance ++ */ ++ private static SAXParser getSAXParser() { ++ SoftReference ref = PARSER.get(); ++ SAXParser result = ref.get(); ++ if (result == null) { ++ Exception thrown; ++ try { ++ result = SAX_FACTORY.newSAXParser(); ++ ref = new SoftReference(result); ++ PARSER.set(ref); ++ return result; ++ } catch (ParserConfigurationException ex) { ++ thrown = ex; ++ } catch (SAXException ex) { ++ thrown = ex; ++ } ++ throw(new IllegalStateException( ++ "Could not create SAX parser", thrown)); ++ } else { ++ result.reset(); ++ return result; ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParserXmlPull.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParserXmlPull.java +new file mode 100644 +index 0000000..5f23b06 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParserXmlPull.java +@@ -0,0 +1,165 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.io.IOException; ++import java.io.StringReader; ++import java.lang.ref.SoftReference; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++import javax.xml.XMLConstants; ++import org.xmlpull.v1.XmlPullParser; ++import org.xmlpull.v1.XmlPullParserException; ++import org.xmlpull.v1.XmlPullParserFactory; ++ ++/** ++ * Implementation of the BodyParser interface which uses the XmlPullParser ++ * API. When available, this API provides an order of magnitude performance ++ * improvement over the default SAX parser implementation. ++ */ ++final class BodyParserXmlPull implements BodyParser { ++ ++ /** ++ * Logger. ++ */ ++ private static final Logger LOG = ++ Logger.getLogger(BodyParserXmlPull.class.getName()); ++ ++ /** ++ * Thread local to contain a XmlPullParser instance for each thread that ++ * attempts to use one. This allows us to gain an order of magnitude of ++ * performance as a result of not constructing parsers for each ++ * invocation while retaining thread safety. ++ */ ++ private static final ThreadLocal> XPP_PARSER = ++ new ThreadLocal>() { ++ @Override protected SoftReference initialValue() { ++ return new SoftReference(null); ++ } ++ }; ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // BodyParser interface methods: ++ ++ /** ++ * {@inheritDoc} ++ */ ++ public BodyParserResults parse(final String xml) throws BOSHException { ++ BodyParserResults result = new BodyParserResults(); ++ Exception thrown; ++ try { ++ XmlPullParser xpp = getXmlPullParser(); ++ ++ xpp.setInput(new StringReader(xml)); ++ int eventType = xpp.getEventType(); ++ while (eventType != XmlPullParser.END_DOCUMENT) { ++ if (eventType == XmlPullParser.START_TAG) { ++ if (LOG.isLoggable(Level.FINEST)) { ++ LOG.finest("Start tag: " + xpp.getName()); ++ } ++ } else { ++ eventType = xpp.next(); ++ continue; ++ } ++ ++ String prefix = xpp.getPrefix(); ++ if (prefix == null) { ++ prefix = XMLConstants.DEFAULT_NS_PREFIX; ++ } ++ String uri = xpp.getNamespace(); ++ String localName = xpp.getName(); ++ QName name = new QName(uri, localName, prefix); ++ if (LOG.isLoggable(Level.FINEST)) { ++ LOG.finest("Start element: "); ++ LOG.finest(" prefix: " + prefix); ++ LOG.finest(" URI: " + uri); ++ LOG.finest(" local: " + localName); ++ } ++ ++ BodyQName bodyName = AbstractBody.getBodyQName(); ++ if (!bodyName.equalsQName(name)) { ++ throw(new IllegalStateException( ++ "Root element was not '" + bodyName.getLocalPart() ++ + "' in the '" + bodyName.getNamespaceURI() ++ + "' namespace. (Was '" + localName ++ + "' in '" + uri + "')")); ++ } ++ ++ for (int idx=0; idx < xpp.getAttributeCount(); idx++) { ++ String attrURI = xpp.getAttributeNamespace(idx); ++ if (attrURI.length() == 0) { ++ attrURI = xpp.getNamespace(null); ++ } ++ String attrPrefix = xpp.getAttributePrefix(idx); ++ if (attrPrefix == null) { ++ attrPrefix = XMLConstants.DEFAULT_NS_PREFIX; ++ } ++ String attrLN = xpp.getAttributeName(idx); ++ String attrVal = xpp.getAttributeValue(idx); ++ BodyQName aqn = BodyQName.createWithPrefix( ++ attrURI, attrLN, attrPrefix); ++ if (LOG.isLoggable(Level.FINEST)) { ++ LOG.finest(" Attribute: {" + attrURI + "}" ++ + attrLN + " = '" + attrVal + "'"); ++ } ++ result.addBodyAttributeValue(aqn, attrVal); ++ } ++ break; ++ } ++ return result; ++ } catch (RuntimeException rtx) { ++ thrown = rtx; ++ } catch (XmlPullParserException xmlppx) { ++ thrown = xmlppx; ++ } catch (IOException iox) { ++ thrown = iox; ++ } ++ throw(new BOSHException("Could not parse body:\n" + xml, thrown)); ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Private methods: ++ ++ /** ++ * Gets a XmlPullParser for use in parsing incoming messages. ++ * ++ * @return parser instance ++ */ ++ private static XmlPullParser getXmlPullParser() { ++ SoftReference ref = XPP_PARSER.get(); ++ XmlPullParser result = ref.get(); ++ if (result == null) { ++ Exception thrown; ++ try { ++ XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); ++ factory.setNamespaceAware(true); ++ factory.setValidating(false); ++ result = factory.newPullParser(); ++ ref = new SoftReference(result); ++ XPP_PARSER.set(ref); ++ return result; ++ } catch (Exception ex) { ++ thrown = ex; ++ } ++ throw(new IllegalStateException( ++ "Could not create XmlPull parser", thrown)); ++ } else { ++ return result; ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BodyQName.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyQName.java +new file mode 100644 +index 0000000..83acdf1 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyQName.java +@@ -0,0 +1,165 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * Qualified name of an attribute of the wrapper element. This class is ++ * analagous to the {@code javax.xml.namespace.QName} class. ++ * Each qualified name consists of a namespace URI and a local name. ++ *

++ * Instances of this class are immutable and thread-safe. ++ */ ++public final class BodyQName { ++ ++ /** ++ * BOSH namespace URI. ++ */ ++ static final String BOSH_NS_URI = ++ "http://jabber.org/protocol/httpbind"; ++ ++ /** ++ * Namespace URI. ++ */ ++ private final QName qname; ++ ++ /** ++ * Private constructor to prevent direct construction. ++ * ++ * @param wrapped QName instance to wrap ++ */ ++ private BodyQName( ++ final QName wrapped) { ++ qname = wrapped; ++ } ++ ++ /** ++ * Creates a new qualified name using a namespace URI and local name. ++ * ++ * @param uri namespace URI ++ * @param local local name ++ * @return BodyQName instance ++ */ ++ public static BodyQName create( ++ final String uri, ++ final String local) { ++ return createWithPrefix(uri, local, null); ++ } ++ ++ /** ++ * Creates a new qualified name using a namespace URI and local name ++ * along with an optional prefix. ++ * ++ * @param uri namespace URI ++ * @param local local name ++ * @param prefix optional prefix or @{code null} for no prefix ++ * @return BodyQName instance ++ */ ++ public static BodyQName createWithPrefix( ++ final String uri, ++ final String local, ++ final String prefix) { ++ if (uri == null || uri.length() == 0) { ++ throw(new IllegalArgumentException( ++ "URI is required and may not be null/empty")); ++ } ++ if (local == null || local.length() == 0) { ++ throw(new IllegalArgumentException( ++ "Local arg is required and may not be null/empty")); ++ } ++ if (prefix == null || prefix.length() == 0) { ++ return new BodyQName(new QName(uri, local)); ++ } else { ++ return new BodyQName(new QName(uri, local, prefix)); ++ } ++ } ++ ++ /** ++ * Get the namespace URI of this qualified name. ++ * ++ * @return namespace uri ++ */ ++ public String getNamespaceURI() { ++ return qname.getNamespaceURI(); ++ } ++ ++ /** ++ * Get the local part of this qualified name. ++ * ++ * @return local name ++ */ ++ public String getLocalPart() { ++ return qname.getLocalPart(); ++ } ++ ++ /** ++ * Get the optional prefix used with this qualified name, or {@code null} ++ * if no prefix has been assiciated. ++ * ++ * @return prefix, or {@code null} if no prefix was supplied ++ */ ++ public String getPrefix() { ++ return qname.getPrefix(); ++ } ++ ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public boolean equals(final Object obj) { ++ if (obj instanceof BodyQName) { ++ BodyQName other = (BodyQName) obj; ++ return qname.equals(other.qname); ++ } else { ++ return false; ++ } ++ } ++ ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public int hashCode() { ++ return qname.hashCode(); ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Package-private methods: ++ ++ /** ++ * Creates a new qualified name using the BOSH namespace URI and local name. ++ * ++ * @param local local name ++ * @return BodyQName instance ++ */ ++ static BodyQName createBOSH( ++ final String local) { ++ return createWithPrefix(BOSH_NS_URI, local, null); ++ } ++ ++ /** ++ * Convenience method to compare this qualified name with a ++ * {@code javax.xml.namespace.QName}. ++ * ++ * @param otherName QName to compare to ++ * @return @{code true} if the qualified name is the same, {@code false} ++ * otherwise ++ */ ++ boolean equalsQName(final QName otherName) { ++ return qname.equals(otherName); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/CMSessionParams.java b/external/asmack/build/src/trunk/com/kenai/jbosh/CMSessionParams.java +new file mode 100644 +index 0000000..bbed628 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/CMSessionParams.java +@@ -0,0 +1,177 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * A BOSH connection manager session instance. This consolidates the ++ * configuration knowledge related to the CM session and provides a ++ * mechanism by which ++ */ ++final class CMSessionParams { ++ ++ private final AttrSessionID sid; ++ ++ private final AttrWait wait; ++ ++ private final AttrVersion ver; ++ ++ private final AttrPolling polling; ++ ++ private final AttrInactivity inactivity; ++ ++ private final AttrRequests requests; ++ ++ private final AttrHold hold; ++ ++ private final AttrAccept accept; ++ ++ private final AttrMaxPause maxPause; ++ ++ private final AttrAck ack; ++ ++ private final AttrCharsets charsets; ++ ++ private final boolean ackingRequests; ++ ++ /** ++ * Prevent direct construction. ++ */ ++ private CMSessionParams( ++ final AttrSessionID aSid, ++ final AttrWait aWait, ++ final AttrVersion aVer, ++ final AttrPolling aPolling, ++ final AttrInactivity aInactivity, ++ final AttrRequests aRequests, ++ final AttrHold aHold, ++ final AttrAccept aAccept, ++ final AttrMaxPause aMaxPause, ++ final AttrAck aAck, ++ final AttrCharsets aCharsets, ++ final boolean amAckingRequests) { ++ sid = aSid; ++ wait = aWait; ++ ver = aVer; ++ polling = aPolling; ++ inactivity = aInactivity; ++ requests = aRequests; ++ hold = aHold; ++ accept = aAccept; ++ maxPause = aMaxPause; ++ ack = aAck; ++ charsets = aCharsets; ++ ackingRequests = amAckingRequests; ++ } ++ ++ static CMSessionParams fromSessionInit( ++ final AbstractBody req, ++ final AbstractBody resp) ++ throws BOSHException { ++ AttrAck aAck = AttrAck.createFromString( ++ resp.getAttribute(Attributes.ACK)); ++ String rid = req.getAttribute(Attributes.RID); ++ boolean acking = (aAck != null && aAck.getValue().equals(rid)); ++ ++ return new CMSessionParams( ++ AttrSessionID.createFromString( ++ getRequiredAttribute(resp, Attributes.SID)), ++ AttrWait.createFromString( ++ getRequiredAttribute(resp, Attributes.WAIT)), ++ AttrVersion.createFromString( ++ resp.getAttribute(Attributes.VER)), ++ AttrPolling.createFromString( ++ resp.getAttribute(Attributes.POLLING)), ++ AttrInactivity.createFromString( ++ resp.getAttribute(Attributes.INACTIVITY)), ++ AttrRequests.createFromString( ++ resp.getAttribute(Attributes.REQUESTS)), ++ AttrHold.createFromString( ++ resp.getAttribute(Attributes.HOLD)), ++ AttrAccept.createFromString( ++ resp.getAttribute(Attributes.ACCEPT)), ++ AttrMaxPause.createFromString( ++ resp.getAttribute(Attributes.MAXPAUSE)), ++ aAck, ++ AttrCharsets.createFromString( ++ resp.getAttribute(Attributes.CHARSETS)), ++ acking ++ ); ++ } ++ ++ private static String getRequiredAttribute( ++ final AbstractBody body, ++ final BodyQName name) ++ throws BOSHException { ++ String attrStr = body.getAttribute(name); ++ if (attrStr == null) { ++ throw(new BOSHException( ++ "Connection Manager session creation response did not " ++ + "include required '" + name.getLocalPart() ++ + "' attribute")); ++ } ++ return attrStr; ++ } ++ ++ AttrSessionID getSessionID() { ++ return sid; ++ } ++ ++ AttrWait getWait() { ++ return wait; ++ } ++ ++ AttrVersion getVersion() { ++ return ver; ++ } ++ ++ AttrPolling getPollingInterval() { ++ return polling; ++ } ++ ++ AttrInactivity getInactivityPeriod() { ++ return inactivity; ++ } ++ ++ AttrRequests getRequests() { ++ return requests; ++ } ++ ++ AttrHold getHold() { ++ return hold; ++ } ++ ++ AttrAccept getAccept() { ++ return accept; ++ } ++ ++ AttrMaxPause getMaxPause() { ++ return maxPause; ++ } ++ ++ AttrAck getAck() { ++ return ack; ++ } ++ ++ AttrCharsets getCharsets() { ++ return charsets; ++ } ++ ++ boolean isAckingRequests() { ++ return ackingRequests; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/ComposableBody.java b/external/asmack/build/src/trunk/com/kenai/jbosh/ComposableBody.java +new file mode 100644 +index 0000000..7f3b159 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/ComposableBody.java +@@ -0,0 +1,345 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.Map; ++import java.util.concurrent.atomic.AtomicReference; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++import javax.xml.XMLConstants; ++ ++/** ++ * Implementation of the {@code AbstractBody} class which allows for the ++ * definition of messages from individual elements of a body. ++ *

++ * A message is constructed by creating a builder, manipulating the ++ * configuration of the builder, and then building it into a class instance, ++ * as in the following example: ++ *

++ * ComposableBody body = ComposableBody.builder()
++ *     .setNamespaceDefinition("foo", "http://foo.com/bar")
++ *     .setPayloadXML("Data to send to remote server")
++ *     .build();
++ * 
++ * Class instances can also be "rebuilt", allowing them to be used as templates ++ * when building many similar messages: ++ *
++ * ComposableBody body2 = body.rebuild()
++ *     .setPayloadXML("More data to send")
++ *     .build();
++ * 
++ * This class does only minimal syntactic and semantic checking with respect ++ * to what the generated XML will look like. It is up to the developer to ++ * protect against the definition of malformed XML messages when building ++ * instances of this class. ++ *

++ * Instances of this class are immutable and thread-safe. ++ */ ++public final class ComposableBody extends AbstractBody { ++ ++ /** ++ * Pattern used to identify the beginning {@code body} element of a ++ * BOSH message. ++ */ ++ private static final Pattern BOSH_START = ++ Pattern.compile("<" ++ + "body" + "(?:[\t\n\r ][^>]*?)?" + "(/>|>)", Pattern.UNICODE_CASE); ++ ++ /** ++ * Map of all attributes to their values. ++ */ ++ private final Map attrs; ++ ++ /** ++ * Payload XML. ++ */ ++ private final String payload; ++ ++ /** ++ * Computed raw XML. ++ */ ++ private final AtomicReference computed = ++ new AtomicReference(); ++ ++ /** ++ * Class instance builder, after the builder pattern. This allows each ++ * message instance to be immutable while providing flexibility when ++ * building new messages. ++ *

++ * Instances of this class are not thread-safe. ++ */ ++ public static final class Builder { ++ private Map map; ++ private boolean doMapCopy; ++ private String payloadXML; ++ ++ /** ++ * Prevent direct construction. ++ */ ++ private Builder() { ++ // Empty ++ } ++ ++ /** ++ * Creates a builder which is initialized to the values of the ++ * provided {@code ComposableBody} instance. This allows an ++ * existing {@code ComposableBody} to be used as a ++ * template/starting point. ++ * ++ * @param source body template ++ * @return builder instance ++ */ ++ private static Builder fromBody(final ComposableBody source) { ++ Builder result = new Builder(); ++ result.map = source.getAttributes(); ++ result.doMapCopy = true; ++ result.payloadXML = source.payload; ++ return result; ++ } ++ ++ /** ++ * Set the body message's wrapped payload content. Any previous ++ * content will be replaced. ++ * ++ * @param xml payload XML content ++ * @return builder instance ++ */ ++ public Builder setPayloadXML(final String xml) { ++ if (xml == null) { ++ throw(new IllegalArgumentException( ++ "payload XML argument cannot be null")); ++ } ++ payloadXML = xml; ++ return this; ++ } ++ ++ /** ++ * Set an attribute on the message body / wrapper element. ++ * ++ * @param name qualified name of the attribute ++ * @param value value of the attribute ++ * @return builder instance ++ */ ++ public Builder setAttribute( ++ final BodyQName name, final String value) { ++ if (map == null) { ++ map = new HashMap(); ++ } else if (doMapCopy) { ++ map = new HashMap(map); ++ doMapCopy = false; ++ } ++ if (value == null) { ++ map.remove(name); ++ } else { ++ map.put(name, value); ++ } ++ return this; ++ } ++ ++ /** ++ * Convenience method to set a namespace definition. This would result ++ * in a namespace prefix definition similar to: ++ * {@code } ++ * ++ * @param prefix prefix to define ++ * @param uri namespace URI to associate with the prefix ++ * @return builder instance ++ */ ++ public Builder setNamespaceDefinition( ++ final String prefix, final String uri) { ++ BodyQName qname = BodyQName.createWithPrefix( ++ XMLConstants.XML_NS_URI, prefix, ++ XMLConstants.XMLNS_ATTRIBUTE); ++ return setAttribute(qname, uri); ++ } ++ ++ /** ++ * Build the immutable object instance with the current configuration. ++ * ++ * @return composable body instance ++ */ ++ public ComposableBody build() { ++ if (map == null) { ++ map = new HashMap(); ++ } ++ if (payloadXML == null) { ++ payloadXML = ""; ++ } ++ return new ComposableBody(map, payloadXML); ++ } ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Constructors: ++ ++ /** ++ * Prevent direct construction. This constructor is for body messages ++ * which are dynamically assembled. ++ */ ++ private ComposableBody( ++ final Map attrMap, ++ final String payloadXML) { ++ super(); ++ attrs = attrMap; ++ payload = payloadXML; ++ } ++ ++ /** ++ * Parse a static body instance into a composable instance. This is an ++ * expensive operation and should not be used lightly. ++ *

++ * The current implementation does not obtain the payload XML by means of ++ * a proper XML parser. It uses some string pattern searching to find the ++ * first @{code body} element and the last element's closing tag. It is ++ * assumed that the static body's XML is well formed, etc.. This ++ * implementation may change in the future. ++ * ++ * @param body static body instance to convert ++ * @return composable bosy instance ++ * @throws BOSHException ++ */ ++ static ComposableBody fromStaticBody(final StaticBody body) ++ throws BOSHException { ++ String raw = body.toXML(); ++ Matcher matcher = BOSH_START.matcher(raw); ++ if (!matcher.find()) { ++ throw(new BOSHException( ++ "Could not locate 'body' element in XML. The raw XML did" ++ + " not match the pattern: " + BOSH_START)); ++ } ++ String payload; ++ if (">".equals(matcher.group(1))) { ++ int first = matcher.end(); ++ int last = raw.lastIndexOf(" getAttributes() { ++ return Collections.unmodifiableMap(attrs); ++ } ++ ++ /** ++ * {@inheritDoc} ++ */ ++ public String toXML() { ++ String comp = computed.get(); ++ if (comp == null) { ++ comp = computeXML(); ++ computed.set(comp); ++ } ++ return comp; ++ } ++ ++ /** ++ * Get the paylaod XML in String form. ++ * ++ * @return payload XML ++ */ ++ public String getPayloadXML() { ++ return payload; ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Private methods: ++ ++ /** ++ * Escape the value of an attribute to ensure we maintain valid ++ * XML syntax. ++ * ++ * @param value value to escape ++ * @return escaped value ++ */ ++ private String escape(final String value) { ++ return value.replace("'", "'"); ++ } ++ ++ /** ++ * Generate a String representation of the message body. ++ * ++ * @return XML string representation of the body ++ */ ++ private String computeXML() { ++ BodyQName bodyName = getBodyQName(); ++ StringBuilder builder = new StringBuilder(); ++ builder.append("<"); ++ builder.append(bodyName.getLocalPart()); ++ for (Map.Entry entry : attrs.entrySet()) { ++ builder.append(" "); ++ BodyQName name = entry.getKey(); ++ String prefix = name.getPrefix(); ++ if (prefix != null && prefix.length() > 0) { ++ builder.append(prefix); ++ builder.append(":"); ++ } ++ builder.append(name.getLocalPart()); ++ builder.append("='"); ++ builder.append(escape(entry.getValue())); ++ builder.append("'"); ++ } ++ builder.append(" "); ++ builder.append(XMLConstants.XMLNS_ATTRIBUTE); ++ builder.append("='"); ++ builder.append(bodyName.getNamespaceURI()); ++ builder.append("'>"); ++ if (payload != null) { ++ builder.append(payload); ++ } ++ builder.append(""); ++ return builder.toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/GZIPCodec.java b/external/asmack/build/src/trunk/com/kenai/jbosh/GZIPCodec.java +new file mode 100644 +index 0000000..988f27f +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/GZIPCodec.java +@@ -0,0 +1,104 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.io.ByteArrayInputStream; ++import java.io.ByteArrayOutputStream; ++import java.io.IOException; ++import java.util.zip.GZIPInputStream; ++import java.util.zip.GZIPOutputStream; ++ ++/** ++ * Codec methods for compressing and uncompressing using GZIP. ++ */ ++final class GZIPCodec { ++ ++ /** ++ * Size of the internal buffer when decoding. ++ */ ++ private static final int BUFFER_SIZE = 512; ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Constructors: ++ ++ /** ++ * Prevent construction. ++ */ ++ private GZIPCodec() { ++ // Empty ++ } ++ ++ /** ++ * Returns the name of the codec. ++ * ++ * @return string name of the codec (i.e., "gzip") ++ */ ++ public static String getID() { ++ return "gzip"; ++ } ++ ++ /** ++ * Compress/encode the data provided using the GZIP format. ++ * ++ * @param data data to compress ++ * @return compressed data ++ * @throws IOException on compression failure ++ */ ++ public static byte[] encode(final byte[] data) throws IOException { ++ ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); ++ GZIPOutputStream gzOut = null; ++ try { ++ gzOut = new GZIPOutputStream(byteOut); ++ gzOut.write(data); ++ gzOut.close(); ++ byteOut.close(); ++ return byteOut.toByteArray(); ++ } finally { ++ gzOut.close(); ++ byteOut.close(); ++ } ++ } ++ ++ /** ++ * Uncompress/decode the data provided using the GZIP format. ++ * ++ * @param data data to uncompress ++ * @return uncompressed data ++ * @throws IOException on decompression failure ++ */ ++ public static byte[] decode(final byte[] compressed) throws IOException { ++ ByteArrayInputStream byteIn = new ByteArrayInputStream(compressed); ++ ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); ++ GZIPInputStream gzIn = null; ++ try { ++ gzIn = new GZIPInputStream(byteIn); ++ int read; ++ byte[] buffer = new byte[BUFFER_SIZE]; ++ do { ++ read = gzIn.read(buffer); ++ if (read > 0) { ++ byteOut.write(buffer, 0, read); ++ } ++ } while (read >= 0); ++ return byteOut.toByteArray(); ++ } finally { ++ gzIn.close(); ++ byteOut.close(); ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/HTTPExchange.java b/external/asmack/build/src/trunk/com/kenai/jbosh/HTTPExchange.java +new file mode 100644 +index 0000000..c77caf0 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/HTTPExchange.java +@@ -0,0 +1,126 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.util.concurrent.locks.Condition; ++import java.util.concurrent.locks.Lock; ++import java.util.concurrent.locks.ReentrantLock; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++/** ++ * A request and response pair representing a single exchange with a remote ++ * content manager. This is primarily a container class intended to maintain ++ * the relationship between the request and response but allows the response ++ * to be added after the fact. ++ */ ++final class HTTPExchange { ++ ++ /** ++ * Logger. ++ */ ++ private static final Logger LOG = ++ Logger.getLogger(HTTPExchange.class.getName()); ++ ++ /** ++ * Request body. ++ */ ++ private final AbstractBody request; ++ ++ /** ++ * Lock instance used to protect and provide conditions. ++ */ ++ private final Lock lock = new ReentrantLock(); ++ ++ /** ++ * Condition used to signal when the response has been set. ++ */ ++ private final Condition ready = lock.newCondition(); ++ ++ /** ++ * HTTPResponse instance. ++ */ ++ private HTTPResponse response; ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Constructor: ++ ++ /** ++ * Create a new request/response pair object. ++ * ++ * @param req request message body ++ */ ++ HTTPExchange(final AbstractBody req) { ++ if (req == null) { ++ throw(new IllegalArgumentException("Request body cannot be null")); ++ } ++ request = req; ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Package-private methods: ++ ++ /** ++ * Get the original request message. ++ * ++ * @return request message body. ++ */ ++ AbstractBody getRequest() { ++ return request; ++ } ++ ++ /** ++ * Set the HTTPResponse instance. ++ * ++ * @return HTTPResponse instance associated with the request. ++ */ ++ void setHTTPResponse(HTTPResponse resp) { ++ lock.lock(); ++ try { ++ if (response != null) { ++ throw(new IllegalStateException( ++ "HTTPResponse was already set")); ++ } ++ response = resp; ++ ready.signalAll(); ++ } finally { ++ lock.unlock(); ++ } ++ } ++ ++ /** ++ * Get the HTTPResponse instance. ++ * ++ * @return HTTPResponse instance associated with the request. ++ */ ++ HTTPResponse getHTTPResponse() { ++ lock.lock(); ++ try { ++ while (response == null) { ++ try { ++ ready.await(); ++ } catch (InterruptedException intx) { ++ LOG.log(Level.FINEST, "Interrupted", intx); ++ } ++ } ++ return response; ++ } finally { ++ lock.unlock(); ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/HTTPResponse.java b/external/asmack/build/src/trunk/com/kenai/jbosh/HTTPResponse.java +new file mode 100644 +index 0000000..f1f301c +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/HTTPResponse.java +@@ -0,0 +1,54 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * This class represents a complete HTTP response to a request made via ++ * a {@code HTTPSender} send request. Instances of this interface are ++ * intended to represent a deferred, future response, not necessarily a ++ * response which is immediately available. ++ */ ++interface HTTPResponse { ++ ++ /** ++ * Close out any resources still held by the original request. The ++ * conversation may need to be aborted if the session it was a part of ++ * gets abruptly terminated. ++ */ ++ void abort(); ++ ++ /** ++ * Get the HTTP status code of the response (e.g., 200, 404, etc.). If ++ * the response has not yet been received from the remote server, this ++ * method should block until the response has arrived. ++ * ++ * @return HTTP status code ++ * @throws InterruptedException if interrupted while awaiting response ++ */ ++ int getHTTPStatus() throws InterruptedException, BOSHException; ++ ++ /** ++ * Get the HTTP response message body. If the response has not yet been ++ * received from the remote server, this method should block until the ++ * response has arrived. ++ * ++ * @return response message body ++ * @throws InterruptedException if interrupted while awaiting response ++ */ ++ AbstractBody getBody() throws InterruptedException, BOSHException; ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/HTTPSender.java b/external/asmack/build/src/trunk/com/kenai/jbosh/HTTPSender.java +new file mode 100644 +index 0000000..486d274 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/HTTPSender.java +@@ -0,0 +1,54 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++/** ++ * Interface used to represent code which can send a BOSH XML body over ++ * HTTP to a connection manager. ++ */ ++interface HTTPSender { ++ ++ /** ++ * Initialize the HTTP sender instance for use with the session provided. ++ * This method will be called once before use of the service instance. ++ * ++ * @param sessionCfg session configuration ++ */ ++ void init(BOSHClientConfig sessionCfg); ++ ++ /** ++ * Dispose of all resources used to provide the required services. This ++ * method will be called once when the service instance is no longer ++ * required. ++ */ ++ void destroy(); ++ ++ /** ++ * Create a {@code Callable} instance which can be used to send the ++ * request specified to the connection manager. This method should ++ * return immediately, prior to doing any real work. The invocation ++ * of the returned {@code Callable} should send the request (if it has ++ * not already been sent by the time of the call), block while waiting ++ * for the response, and then return the response body. ++ * ++ * @param params CM session creation resopnse params ++ * @param body request body to send ++ * @return callable used to access the response ++ */ ++ HTTPResponse send(CMSessionParams params, AbstractBody body); ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/QName.java b/external/asmack/build/src/trunk/com/kenai/jbosh/QName.java +new file mode 100644 +index 0000000..d395a06 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/QName.java +@@ -0,0 +1,269 @@ ++/* ++ * The Apache Software License, Version 1.1 ++ * ++ * ++ * Copyright (c) 2001-2003 The Apache Software Foundation. All rights ++ * reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in ++ * the documentation and/or other materials provided with the ++ * distribution. ++ * ++ * 3. The end-user documentation included with the redistribution, ++ * if any, must include the following acknowledgment: ++ * "This product includes software developed by the ++ * Apache Software Foundation (http://www.apache.org/)." ++ * Alternately, this acknowledgment may appear in the software itself, ++ * if and wherever such third-party acknowledgments normally appear. ++ * ++ * 4. The names "Axis" and "Apache Software Foundation" must ++ * not be used to endorse or promote products derived from this ++ * software without prior written permission. For written ++ * permission, please contact apache@apache.org. ++ * ++ * 5. Products derived from this software may not be called "Apache", ++ * nor may "Apache" appear in their name, without prior written ++ * permission of the Apache Software Foundation. ++ * ++ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED ++ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR ++ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ++ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ++ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++ * SUCH DAMAGE. ++ * ==================================================================== ++ * ++ * This software consists of voluntary contributions made by many ++ * individuals on behalf of the Apache Software Foundation. For more ++ * information on the Apache Software Foundation, please see ++ * . ++ */ ++package com.kenai.jbosh; ++ ++import java.io.IOException; ++import java.io.ObjectInputStream; ++import java.io.Serializable; ++ ++/** ++ * QName class represents the value of a qualified name ++ * as specified in XML ++ * Schema Part2: Datatypes specification. ++ *

++ * The value of a QName contains a namespaceURI, a localPart and a prefix. ++ * The localPart provides the local part of the qualified name. The ++ * namespaceURI is a URI reference identifying the namespace. ++ * ++ * @version 1.1 ++ */ ++public class QName implements Serializable { ++ ++ /** comment/shared empty string */ ++ private static final String emptyString = "".intern(); ++ ++ /** Field namespaceURI */ ++ private String namespaceURI; ++ ++ /** Field localPart */ ++ private String localPart; ++ ++ /** Field prefix */ ++ private String prefix; ++ ++ /** ++ * Constructor for the QName. ++ * ++ * @param localPart Local part of the QName ++ */ ++ public QName(String localPart) { ++ this(emptyString, localPart, emptyString); ++ } ++ ++ /** ++ * Constructor for the QName. ++ * ++ * @param namespaceURI Namespace URI for the QName ++ * @param localPart Local part of the QName. ++ */ ++ public QName(String namespaceURI, String localPart) { ++ this(namespaceURI, localPart, emptyString); ++ } ++ ++ /** ++ * Constructor for the QName. ++ * ++ * @param namespaceURI Namespace URI for the QName ++ * @param localPart Local part of the QName. ++ * @param prefix Prefix of the QName. ++ */ ++ public QName(String namespaceURI, String localPart, String prefix) { ++ this.namespaceURI = (namespaceURI == null) ++ ? emptyString ++ : namespaceURI.intern(); ++ if (localPart == null) { ++ throw new IllegalArgumentException("invalid QName local part"); ++ } else { ++ this.localPart = localPart.intern(); ++ } ++ ++ if (prefix == null) { ++ throw new IllegalArgumentException("invalid QName prefix"); ++ } else { ++ this.prefix = prefix.intern(); ++ } ++ } ++ ++ /** ++ * Gets the Namespace URI for this QName ++ * ++ * @return Namespace URI ++ */ ++ public String getNamespaceURI() { ++ return namespaceURI; ++ } ++ ++ /** ++ * Gets the Local part for this QName ++ * ++ * @return Local part ++ */ ++ public String getLocalPart() { ++ return localPart; ++ } ++ ++ /** ++ * Gets the Prefix for this QName ++ * ++ * @return Prefix ++ */ ++ public String getPrefix() { ++ return prefix; ++ } ++ ++ /** ++ * Returns a string representation of this QName ++ * ++ * @return a string representation of the QName ++ */ ++ public String toString() { ++ ++ return ((namespaceURI == emptyString) ++ ? localPart ++ : '{' + namespaceURI + '}' + localPart); ++ } ++ ++ /** ++ * Tests this QName for equality with another object. ++ *

++ * If the given object is not a QName or is null then this method ++ * returns false. ++ *

++ * For two QNames to be considered equal requires that both ++ * localPart and namespaceURI must be equal. This method uses ++ * String.equals to check equality of localPart ++ * and namespaceURI. Any class that extends QName is required ++ * to satisfy this equality contract. ++ *

++ * This method satisfies the general contract of the Object.equals method. ++ * ++ * @param obj the reference object with which to compare ++ * ++ * @return true if the given object is identical to this ++ * QName: false otherwise. ++ */ ++ public final boolean equals(Object obj) { ++ ++ if (obj == this) { ++ return true; ++ } ++ ++ if (!(obj instanceof QName)) { ++ return false; ++ } ++ ++ if ((namespaceURI == ((QName) obj).namespaceURI) ++ && (localPart == ((QName) obj).localPart)) { ++ return true; ++ } ++ ++ return false; ++ } ++ ++ /** ++ * Returns a QName holding the value of the specified String. ++ *

++ * The string must be in the form returned by the QName.toString() ++ * method, i.e. "{namespaceURI}localPart", with the "{namespaceURI}" ++ * part being optional. ++ *

++ * This method doesn't do a full validation of the resulting QName. ++ * In particular, it doesn't check that the resulting namespace URI ++ * is a legal URI (per RFC 2396 and RFC 2732), nor that the resulting ++ * local part is a legal NCName per the XML Namespaces specification. ++ * ++ * @param s the string to be parsed ++ * @throws java.lang.IllegalArgumentException If the specified String cannot be parsed as a QName ++ * @return QName corresponding to the given String ++ */ ++ public static QName valueOf(String s) { ++ ++ if ((s == null) || s.equals("")) { ++ throw new IllegalArgumentException("invalid QName literal"); ++ } ++ ++ if (s.charAt(0) == '{') { ++ int i = s.indexOf('}'); ++ ++ if (i == -1) { ++ throw new IllegalArgumentException("invalid QName literal"); ++ } ++ ++ if (i == s.length() - 1) { ++ throw new IllegalArgumentException("invalid QName literal"); ++ } else { ++ return new QName(s.substring(1, i), s.substring(i + 1)); ++ } ++ } else { ++ return new QName(s); ++ } ++ } ++ ++ /** ++ * Returns a hash code value for this QName object. The hash code ++ * is based on both the localPart and namespaceURI parts of the ++ * QName. This method satisfies the general contract of the ++ * Object.hashCode method. ++ * ++ * @return a hash code value for this Qname object ++ */ ++ public final int hashCode() { ++ return namespaceURI.hashCode() ^ localPart.hashCode(); ++ } ++ ++ /** ++ * Ensure that deserialization properly interns the results. ++ * @param in the ObjectInputStream to be read ++ */ ++ private void readObject(ObjectInputStream in) throws ++ IOException, ClassNotFoundException { ++ in.defaultReadObject(); ++ ++ namespaceURI = namespaceURI.intern(); ++ localPart = localPart.intern(); ++ prefix = prefix.intern(); ++ } ++} ++ +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/RequestIDSequence.java b/external/asmack/build/src/trunk/com/kenai/jbosh/RequestIDSequence.java +new file mode 100644 +index 0000000..14b1475 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/RequestIDSequence.java +@@ -0,0 +1,120 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.security.SecureRandom; ++import java.util.concurrent.atomic.AtomicLong; ++import java.util.concurrent.locks.Lock; ++import java.util.concurrent.locks.ReentrantLock; ++ ++/** ++ * Request ID sequence generator. This generator generates a random first ++ * RID and then manages the sequence from there on out. ++ */ ++final class RequestIDSequence { ++ ++ /** ++ * Maximum number of bits available for representing request IDs, according ++ * to the XEP-0124 spec.s ++ */ ++ private static final int MAX_BITS = 53; ++ ++ /** ++ * Bits devoted to incremented values. ++ */ ++ private static final int INCREMENT_BITS = 32; ++ ++ /** ++ * Minimum number of times the initial RID can be incremented before ++ * exceeding the maximum. ++ */ ++ private static final long MIN_INCREMENTS = 1L << INCREMENT_BITS; ++ ++ /** ++ * Max initial value. ++ */ ++ private static final long MAX_INITIAL = (1L << MAX_BITS) - MIN_INCREMENTS; ++ ++ /** ++ * Max bits mask. ++ */ ++ private static final long MASK = ~(Long.MAX_VALUE << MAX_BITS); ++ ++ /** ++ * Random number generator. ++ */ ++ private static final SecureRandom RAND = new SecureRandom(); ++ ++ /** ++ * Internal lock. ++ */ ++ private static final Lock LOCK = new ReentrantLock(); ++ ++ /** ++ * The last reqest ID used, or <= 0 if a new request ID needs to be ++ * generated. ++ */ ++ private AtomicLong nextRequestID = new AtomicLong(); ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Constructors: ++ ++ /** ++ * Prevent direct construction. ++ */ ++ RequestIDSequence() { ++ nextRequestID = new AtomicLong(generateInitialValue()); ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Public methods: ++ ++ /** ++ * Calculates the next request ID value to use. This number must be ++ * initialized such that it is unlikely to ever exceed 2 ^ 53, according ++ * to XEP-0124. ++ * ++ * @return next request ID value ++ */ ++ public long getNextRID() { ++ return nextRequestID.getAndIncrement(); ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Private methods: ++ ++ /** ++ * Generates an initial RID value by generating numbers until a number is ++ * found which is smaller than the maximum allowed value and greater ++ * than zero. ++ * ++ * @return random initial value ++ */ ++ private long generateInitialValue() { ++ long result; ++ LOCK.lock(); ++ try { ++ do { ++ result = RAND.nextLong() & MASK; ++ } while (result > MAX_INITIAL); ++ } finally { ++ LOCK.unlock(); ++ } ++ return result; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/ServiceLib.java b/external/asmack/build/src/trunk/com/kenai/jbosh/ServiceLib.java +new file mode 100644 +index 0000000..07d0556 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/ServiceLib.java +@@ -0,0 +1,195 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.io.BufferedReader; ++import java.io.Closeable; ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.InputStreamReader; ++import java.net.URL; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++/** ++ * Utility library for use in loading services using the Jar Service ++ * Provider Interface (Jar SPI). This can be replaced once the minimum ++ * java rev moves beyond Java 5. ++ */ ++final class ServiceLib { ++ ++ /** ++ * Logger. ++ */ ++ private static final Logger LOG = ++ Logger.getLogger(ServiceLib.class.getName()); ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Package-private methods: ++ ++ /** ++ * Prevent construction. ++ */ ++ private ServiceLib() { ++ // Empty ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Package-private methods: ++ ++ /** ++ * Probe for and select an implementation of the specified service ++ * type by using the a modified Jar SPI mechanism. Modified in that ++ * the system properties will be checked to see if there is a value ++ * set for the naem of the class to be loaded. If so, that value is ++ * treated as the class name of the first implementation class to be ++ * attempted to be loaded. This provides a (unsupported) mechanism ++ * to insert other implementations. Note that the supported mechanism ++ * is by properly ordering the classpath. ++ * ++ * @return service instance ++ * @throws IllegalStateException is no service implementations could be ++ * instantiated ++ */ ++ static T loadService(Class ofType) { ++ List implClasses = loadServicesImplementations(ofType); ++ for (String implClass : implClasses) { ++ T result = attemptLoad(ofType, implClass); ++ if (result != null) { ++ if (LOG.isLoggable(Level.FINEST)) { ++ LOG.finest("Selected " + ofType.getSimpleName() ++ + " implementation: " ++ + result.getClass().getName()); ++ } ++ return result; ++ } ++ } ++ throw(new IllegalStateException( ++ "Could not load " + ofType.getName() + " implementation")); ++ } ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Private methods: ++ ++ /** ++ * Generates a list of implementation class names by using ++ * the Jar SPI technique. The order in which the class names occur ++ * in the service manifest is significant. ++ * ++ * @return list of all declared implementation class names ++ */ ++ private static List loadServicesImplementations( ++ final Class ofClass) { ++ List result = new ArrayList(); ++ ++ // Allow a sysprop to specify the first candidate ++ String override = System.getProperty(ofClass.getName()); ++ if (override != null) { ++ result.add(override); ++ } ++ ++ ClassLoader loader = ServiceLib.class.getClassLoader(); ++ URL url = loader.getResource("META-INF/services/" + ofClass.getName()); ++ InputStream inStream = null; ++ InputStreamReader reader = null; ++ BufferedReader bReader = null; ++ try { ++ inStream = url.openStream(); ++ reader = new InputStreamReader(inStream); ++ bReader = new BufferedReader(reader); ++ String line; ++ while ((line = bReader.readLine()) != null) { ++ if (!line.matches("\\s*(#.*)?")) { ++ // not a comment or blank line ++ result.add(line.trim()); ++ } ++ } ++ } catch (IOException iox) { ++ LOG.log(Level.WARNING, ++ "Could not load services descriptor: " + url.toString(), ++ iox); ++ } finally { ++ finalClose(bReader); ++ finalClose(reader); ++ finalClose(inStream); ++ } ++ return result; ++ } ++ ++ /** ++ * Attempts to load the specified implementation class. ++ * Attempts will fail if - for example - the implementation depends ++ * on a class not found on the classpath. ++ * ++ * @param className implementation class to attempt to load ++ * @return service instance, or {@code null} if the instance could not be ++ * loaded ++ */ ++ private static T attemptLoad( ++ final Class ofClass, ++ final String className) { ++ if (LOG.isLoggable(Level.FINEST)) { ++ LOG.finest("Attempting service load: " + className); ++ } ++ Level level; ++ Exception thrown; ++ try { ++ Class clazz = Class.forName(className); ++ if (!ofClass.isAssignableFrom(clazz)) { ++ if (LOG.isLoggable(Level.WARNING)) { ++ LOG.warning(clazz.getName() + " is not assignable to " ++ + ofClass.getName()); ++ } ++ return null; ++ } ++ return ofClass.cast(clazz.newInstance()); ++ } catch (ClassNotFoundException ex) { ++ level = Level.FINEST; ++ thrown = ex; ++ } catch (InstantiationException ex) { ++ level = Level.WARNING; ++ thrown = ex; ++ } catch (IllegalAccessException ex) { ++ level = Level.WARNING; ++ thrown = ex; ++ } ++ LOG.log(level, ++ "Could not load " + ofClass.getSimpleName() ++ + " instance: " + className, ++ thrown); ++ return null; ++ } ++ ++ /** ++ * Check and close a closeable object, trapping and ignoring any ++ * exception that might result. ++ * ++ * @param closeMe the thing to close ++ */ ++ private static void finalClose(final Closeable closeMe) { ++ if (closeMe != null) { ++ try { ++ closeMe.close(); ++ } catch (IOException iox) { ++ LOG.log(Level.FINEST, "Could not close: " + closeMe, iox); ++ } ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/StaticBody.java b/external/asmack/build/src/trunk/com/kenai/jbosh/StaticBody.java +new file mode 100644 +index 0000000..fe225fb +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/StaticBody.java +@@ -0,0 +1,133 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.io.ByteArrayOutputStream; ++import java.io.IOException; ++import java.io.InputStream; ++import java.util.Collections; ++import java.util.Map; ++ ++/** ++ * Implementation of the {@code AbstractBody} class which allows for the ++ * definition of messages from pre-existing message content. Instances of ++ * this class are based on the underlying data and therefore cannot be ++ * modified. In order to obtain the wrapper element namespace and ++ * attribute information, the body content is partially parsed. ++ *

++ * This class does only minimal syntactic and semantic checking with respect ++ * to what the generated XML will look like. It is up to the developer to ++ * protect against the definition of malformed XML messages when building ++ * instances of this class. ++ *

++ * Instances of this class are immutable and thread-safe. ++ */ ++final class StaticBody extends AbstractBody { ++ ++ /** ++ * Selected parser to be used to process raw XML messages. ++ */ ++ private static final BodyParser PARSER = ++ new BodyParserXmlPull(); ++ ++ /** ++ * Size of the internal buffer when copying from a stream. ++ */ ++ private static final int BUFFER_SIZE = 1024; ++ ++ /** ++ * Map of all attributes to their values. ++ */ ++ private final Map attrs; ++ ++ /** ++ * This body message in raw XML form. ++ */ ++ private final String raw; ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Constructors: ++ ++ /** ++ * Prevent direct construction. ++ */ ++ private StaticBody( ++ final Map attrMap, ++ final String rawXML) { ++ attrs = attrMap; ++ raw = rawXML; ++ } ++ ++ /** ++ * Creates an instance which is initialized by reading a body ++ * message from the provided stream. ++ * ++ * @param inStream stream to read message XML from ++ * @return body instance ++ * @throws BOSHException on parse error ++ */ ++ public static StaticBody fromStream( ++ final InputStream inStream) ++ throws BOSHException { ++ ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); ++ try { ++ byte[] buffer = new byte[BUFFER_SIZE]; ++ int read; ++ do { ++ read = inStream.read(buffer); ++ if (read > 0) { ++ byteOut.write(buffer, 0, read); ++ } ++ } while (read >= 0); ++ } catch (IOException iox) { ++ throw(new BOSHException( ++ "Could not read body data", iox)); ++ } ++ return fromString(byteOut.toString()); ++ } ++ ++ /** ++ * Creates an instance which is initialized by reading a body ++ * message from the provided raw XML string. ++ * ++ * @param rawXML raw message XML in string form ++ * @return body instance ++ * @throws BOSHException on parse error ++ */ ++ public static StaticBody fromString( ++ final String rawXML) ++ throws BOSHException { ++ BodyParserResults results = PARSER.parse(rawXML); ++ return new StaticBody(results.getAttributes(), rawXML); ++ } ++ ++ ++ /** ++ * {@inheritDoc} ++ */ ++ public Map getAttributes() { ++ return Collections.unmodifiableMap(attrs); ++ } ++ ++ /** ++ * {@inheritDoc} ++ */ ++ public String toXML() { ++ return raw; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/TerminalBindingCondition.java b/external/asmack/build/src/trunk/com/kenai/jbosh/TerminalBindingCondition.java +new file mode 100644 +index 0000000..0aecfd8 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/TerminalBindingCondition.java +@@ -0,0 +1,208 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.util.HashMap; ++import java.util.Map; ++ ++/** ++ * Terminal binding conditions and their associated messages. ++ */ ++final class TerminalBindingCondition { ++ ++ /** ++ * Map of condition names to condition instances. ++ */ ++ private static final Map ++ COND_TO_INSTANCE = new HashMap(); ++ ++ /** ++ * Map of HTTP response codes to condition instances. ++ */ ++ private static final Map ++ CODE_TO_INSTANCE = new HashMap(); ++ ++ static final TerminalBindingCondition BAD_REQUEST = ++ createWithCode("bad-request", "The format of an HTTP header or " ++ + "binding element received from the client is unacceptable " ++ + "(e.g., syntax error).", Integer.valueOf(400)); ++ ++ static final TerminalBindingCondition HOST_GONE = ++ create("host-gone", "The target domain specified in the 'to' " ++ + "attribute or the target host or port specified in the 'route' " ++ + "attribute is no longer serviced by the connection manager."); ++ ++ static final TerminalBindingCondition HOST_UNKNOWN = ++ create("host-unknown", "The target domain specified in the 'to' " ++ + "attribute or the target host or port specified in the 'route' " ++ + "attribute is unknown to the connection manager."); ++ ++ static final TerminalBindingCondition IMPROPER_ADDRESSING = ++ create("improper-addressing", "The initialization element lacks a " ++ + "'to' or 'route' attribute (or the attribute has no value) but " ++ + "the connection manager requires one."); ++ ++ static final TerminalBindingCondition INTERNAL_SERVER_ERROR = ++ create("internal-server-error", "The connection manager has " ++ + "experienced an internal error that prevents it from servicing " ++ + "the request."); ++ ++ static final TerminalBindingCondition ITEM_NOT_FOUND = ++ createWithCode("item-not-found", "(1) 'sid' is not valid, (2) " ++ + "'stream' is not valid, (3) 'rid' is larger than the upper limit " ++ + "of the expected window, (4) connection manager is unable to " ++ + "resend response, (5) 'key' sequence is invalid.", ++ Integer.valueOf(404)); ++ ++ static final TerminalBindingCondition OTHER_REQUEST = ++ create("other-request", "Another request being processed at the " ++ + "same time as this request caused the session to terminate."); ++ ++ static final TerminalBindingCondition POLICY_VIOLATION = ++ createWithCode("policy-violation", "The client has broken the " ++ + "session rules (polling too frequently, requesting too " ++ + "frequently, sending too many simultaneous requests).", ++ Integer.valueOf(403)); ++ ++ static final TerminalBindingCondition REMOTE_CONNECTION_FAILED = ++ create("remote-connection-failed", "The connection manager was " ++ + "unable to connect to, or unable to connect securely to, or has " ++ + "lost its connection to, the server."); ++ ++ static final TerminalBindingCondition REMOTE_STREAM_ERROR = ++ create("remote-stream-error", "Encapsulated transport protocol " ++ + "error."); ++ ++ static final TerminalBindingCondition SEE_OTHER_URI = ++ create("see-other-uri", "The connection manager does not operate " ++ + "at this URI (e.g., the connection manager accepts only SSL or " ++ + "TLS connections at some https: URI rather than the http: URI " ++ + "requested by the client)."); ++ ++ static final TerminalBindingCondition SYSTEM_SHUTDOWN = ++ create("system-shutdown", "The connection manager is being shut " ++ + "down. All active HTTP sessions are being terminated. No new " ++ + "sessions can be created."); ++ ++ static final TerminalBindingCondition UNDEFINED_CONDITION = ++ create("undefined-condition", "Unknown or undefined error " ++ + "condition."); ++ ++ /** ++ * Condition name. ++ */ ++ private final String cond; ++ ++ /** ++ * Descriptive message. ++ */ ++ private final String msg; ++ ++ /** ++ * Private constructor to pre ++ */ ++ private TerminalBindingCondition( ++ final String condition, ++ final String message) { ++ cond = condition; ++ msg = message; ++ } ++ ++ /** ++ * Helper method to call the helper method to add entries. ++ */ ++ private static TerminalBindingCondition create( ++ final String condition, ++ final String message) { ++ return createWithCode(condition, message, null); ++ } ++ ++ /** ++ * Helper method to add entries. ++ */ ++ private static TerminalBindingCondition createWithCode( ++ final String condition, ++ final String message, ++ final Integer code) { ++ if (condition == null) { ++ throw(new IllegalArgumentException( ++ "condition may not be null")); ++ } ++ if (message == null) { ++ throw(new IllegalArgumentException( ++ "message may not be null")); ++ } ++ if (COND_TO_INSTANCE.get(condition) != null) { ++ throw(new IllegalStateException( ++ "Multiple definitions of condition: " + condition)); ++ } ++ TerminalBindingCondition result = ++ new TerminalBindingCondition(condition, message); ++ COND_TO_INSTANCE.put(condition, result); ++ if (code != null) { ++ if (CODE_TO_INSTANCE.get(code) != null) { ++ throw(new IllegalStateException( ++ "Multiple definitions of code: " + code)); ++ } ++ CODE_TO_INSTANCE.put(code, result); ++ } ++ return result; ++ } ++ ++ /** ++ * Lookup the terminal binding condition instance with the condition ++ * name specified. ++ * ++ * @param condStr condition name ++ * @return terminal binding condition instance, or {@code null} if no ++ * instance is known by the name specified ++ */ ++ static TerminalBindingCondition forString(final String condStr) { ++ return COND_TO_INSTANCE.get(condStr); ++ } ++ ++ /** ++ * Lookup the terminal binding condition instance associated with the ++ * HTTP response code specified. ++ * ++ * @param httpRespCode HTTP response code ++ * @return terminal binding condition instance, or {@code null} if no ++ * instance is known by the response code specified ++ */ ++ static TerminalBindingCondition forHTTPResponseCode(final int httpRespCode) { ++ return CODE_TO_INSTANCE.get(Integer.valueOf(httpRespCode)); ++ } ++ ++ /** ++ * Get the name of the condition. ++ * ++ * @return condition name ++ */ ++ String getCondition() { ++ return cond; ++ } ++ ++ /** ++ * Get the human readable error message associated with this condition. ++ * ++ * @return error message ++ */ ++ String getMessage() { ++ return msg; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/ZLIBCodec.java b/external/asmack/build/src/trunk/com/kenai/jbosh/ZLIBCodec.java +new file mode 100644 +index 0000000..20844ad +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/ZLIBCodec.java +@@ -0,0 +1,104 @@ ++/* ++ * Copyright 2009 Mike Cumings ++ * ++ * 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 com.kenai.jbosh; ++ ++import java.io.ByteArrayInputStream; ++import java.io.ByteArrayOutputStream; ++import java.io.IOException; ++import java.util.zip.DeflaterOutputStream; ++import java.util.zip.InflaterInputStream; ++ ++/** ++ * Codec methods for compressing and uncompressing using ZLIB. ++ */ ++final class ZLIBCodec { ++ ++ /** ++ * Size of the internal buffer when decoding. ++ */ ++ private static final int BUFFER_SIZE = 512; ++ ++ /////////////////////////////////////////////////////////////////////////// ++ // Constructors: ++ ++ /** ++ * Prevent construction. ++ */ ++ private ZLIBCodec() { ++ // Empty ++ } ++ ++ /** ++ * Returns the name of the codec. ++ * ++ * @return string name of the codec (i.e., "deflate") ++ */ ++ public static String getID() { ++ return "deflate"; ++ } ++ ++ /** ++ * Compress/encode the data provided using the ZLIB format. ++ * ++ * @param data data to compress ++ * @return compressed data ++ * @throws IOException on compression failure ++ */ ++ public static byte[] encode(final byte[] data) throws IOException { ++ ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); ++ DeflaterOutputStream deflateOut = null; ++ try { ++ deflateOut = new DeflaterOutputStream(byteOut); ++ deflateOut.write(data); ++ deflateOut.close(); ++ byteOut.close(); ++ return byteOut.toByteArray(); ++ } finally { ++ deflateOut.close(); ++ byteOut.close(); ++ } ++ } ++ ++ /** ++ * Uncompress/decode the data provided using the ZLIB format. ++ * ++ * @param data data to uncompress ++ * @return uncompressed data ++ * @throws IOException on decompression failure ++ */ ++ public static byte[] decode(final byte[] compressed) throws IOException { ++ ByteArrayInputStream byteIn = new ByteArrayInputStream(compressed); ++ ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); ++ InflaterInputStream inflaterIn = null; ++ try { ++ inflaterIn = new InflaterInputStream(byteIn); ++ int read; ++ byte[] buffer = new byte[BUFFER_SIZE]; ++ do { ++ read = inflaterIn.read(buffer); ++ if (read > 0) { ++ byteOut.write(buffer, 0, read); ++ } ++ } while (read >= 0); ++ return byteOut.toByteArray(); ++ } finally { ++ inflaterIn.close(); ++ byteOut.close(); ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/package.html b/external/asmack/build/src/trunk/com/kenai/jbosh/package.html +new file mode 100644 +index 0000000..77a1924 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/package.html +@@ -0,0 +1,8 @@ ++ ++ ++ Core classes of the JBOSH API. ++

++ Users of the client portion of the API should start by reading ++ up on the BOSHClient documentation. ++ ++ +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/com/novell/sasl/client/DigestChallenge.java b/external/asmack/build/src/trunk/com/novell/sasl/client/DigestChallenge.java +new file mode 100644 +index 0000000..90e6247 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/novell/sasl/client/DigestChallenge.java +@@ -0,0 +1,393 @@ ++/* ************************************************************************** ++ * $OpenLDAP: /com/novell/sasl/client/DigestChallenge.java,v 1.3 2005/01/17 15:00:54 sunilk Exp $ ++ * ++ * Copyright (C) 2003 Novell, Inc. All Rights Reserved. ++ * ++ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND ++ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT ++ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS ++ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE" ++ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION ++ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP ++ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT ++ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY. ++ ******************************************************************************/ ++package com.novell.sasl.client; ++ ++import java.util.*; ++import org.apache.harmony.javax.security.sasl.*; ++ ++/** ++ * Implements the DigestChallenge class which will be used by the ++ * DigestMD5SaslClient class ++ */ ++class DigestChallenge extends Object ++{ ++ public static final int QOP_AUTH = 0x01; ++ public static final int QOP_AUTH_INT = 0x02; ++ public static final int QOP_AUTH_CONF = 0x04; ++ public static final int QOP_UNRECOGNIZED = 0x08; ++ ++ private static final int CIPHER_3DES = 0x01; ++ private static final int CIPHER_DES = 0x02; ++ private static final int CIPHER_RC4_40 = 0x04; ++ private static final int CIPHER_RC4 = 0x08; ++ private static final int CIPHER_RC4_56 = 0x10; ++ private static final int CIPHER_UNRECOGNIZED = 0x20; ++ private static final int CIPHER_RECOGNIZED_MASK = ++ CIPHER_3DES | CIPHER_DES | CIPHER_RC4_40 | CIPHER_RC4 | CIPHER_RC4_56; ++ ++ private ArrayList m_realms; ++ private String m_nonce; ++ private int m_qop; ++ private boolean m_staleFlag; ++ private int m_maxBuf; ++ private String m_characterSet; ++ private String m_algorithm; ++ private int m_cipherOptions; ++ ++ DigestChallenge( ++ byte[] challenge) ++ throws SaslException ++ { ++ m_realms = new ArrayList(5); ++ m_nonce = null; ++ m_qop = 0; ++ m_staleFlag = false; ++ m_maxBuf = -1; ++ m_characterSet = null; ++ m_algorithm = null; ++ m_cipherOptions = 0; ++ ++ DirectiveList dirList = new DirectiveList(challenge); ++ try ++ { ++ dirList.parseDirectives(); ++ checkSemantics(dirList); ++ } ++ catch (SaslException e) ++ { ++ } ++ } ++ ++ /** ++ * Checks the semantics of the directives in the directive list as parsed ++ * from the digest challenge byte array. ++ * ++ * @param dirList the list of directives parsed from the digest challenge ++ * ++ * @exception SaslException If a semantic error occurs ++ */ ++ void checkSemantics( ++ DirectiveList dirList) throws SaslException ++ { ++ Iterator directives = dirList.getIterator(); ++ ParsedDirective directive; ++ String name; ++ ++ while (directives.hasNext()) ++ { ++ directive = (ParsedDirective)directives.next(); ++ name = directive.getName(); ++ if (name.equals("realm")) ++ handleRealm(directive); ++ else if (name.equals("nonce")) ++ handleNonce(directive); ++ else if (name.equals("qop")) ++ handleQop(directive); ++ else if (name.equals("maxbuf")) ++ handleMaxbuf(directive); ++ else if (name.equals("charset")) ++ handleCharset(directive); ++ else if (name.equals("algorithm")) ++ handleAlgorithm(directive); ++ else if (name.equals("cipher")) ++ handleCipher(directive); ++ else if (name.equals("stale")) ++ handleStale(directive); ++ } ++ ++ /* post semantic check */ ++ if (-1 == m_maxBuf) ++ m_maxBuf = 65536; ++ ++ if (m_qop == 0) ++ m_qop = QOP_AUTH; ++ else if ( (m_qop & QOP_AUTH) != QOP_AUTH ) ++ throw new SaslException("Only qop-auth is supported by client"); ++ else if ( ((m_qop & QOP_AUTH_CONF) == QOP_AUTH_CONF) && ++ (0 == (m_cipherOptions & CIPHER_RECOGNIZED_MASK)) ) ++ throw new SaslException("Invalid cipher options"); ++ else if (null == m_nonce) ++ throw new SaslException("Missing nonce directive"); ++ else if (m_staleFlag) ++ throw new SaslException("Unexpected stale flag"); ++ else if ( null == m_algorithm ) ++ throw new SaslException("Missing algorithm directive"); ++ } ++ ++ /** ++ * This function implements the semenatics of the nonce directive. ++ * ++ * @param pd ParsedDirective ++ * ++ * @exception SaslException If an error occurs due to too many nonce ++ * values ++ */ ++ void handleNonce( ++ ParsedDirective pd) throws SaslException ++ { ++ if (null != m_nonce) ++ throw new SaslException("Too many nonce values."); ++ ++ m_nonce = pd.getValue(); ++ } ++ ++ /** ++ * This function implements the semenatics of the realm directive. ++ * ++ * @param pd ParsedDirective ++ */ ++ void handleRealm( ++ ParsedDirective pd) ++ { ++ m_realms.add(pd.getValue()); ++ } ++ ++ /** ++ * This function implements the semenatics of the qop (quality of protection) ++ * directive. The value of the qop directive is as defined below: ++ * qop-options = "qop" "=" <"> qop-list <"> ++ * qop-list = 1#qop-value ++ * qop-value = "auth" | "auth-int" | "auth-conf" | token ++ * ++ * @param pd ParsedDirective ++ * ++ * @exception SaslException If an error occurs due to too many qop ++ * directives ++ */ ++ void handleQop( ++ ParsedDirective pd) throws SaslException ++ { ++ String token; ++ TokenParser parser; ++ ++ if (m_qop != 0) ++ throw new SaslException("Too many qop directives."); ++ ++ parser = new TokenParser(pd.getValue()); ++ for (token = parser.parseToken(); ++ token != null; ++ token = parser.parseToken()) ++ { ++ if (token.equals("auth")) ++ m_qop |= QOP_AUTH; ++ else if (token.equals("auth-int")) ++ m_qop |= QOP_AUTH_INT; ++ else if (token.equals("auth-conf")) ++ m_qop |= QOP_AUTH_CONF; ++ else ++ m_qop |= QOP_UNRECOGNIZED; ++ } ++ } ++ ++ /** ++ * This function implements the semenatics of the Maxbuf directive. ++ * the value is defined as: 1*DIGIT ++ * ++ * @param pd ParsedDirective ++ * ++ * @exception SaslException If an error occur ++ */ ++ void handleMaxbuf( ++ ParsedDirective pd) throws SaslException ++ { ++ if (-1 != m_maxBuf) /*it's initialized to -1 */ ++ throw new SaslException("Too many maxBuf directives."); ++ ++ m_maxBuf = Integer.parseInt(pd.getValue()); ++ ++ if (0 == m_maxBuf) ++ throw new SaslException("Max buf value must be greater than zero."); ++ } ++ ++ /** ++ * This function implements the semenatics of the charset directive. ++ * the value is defined as: 1*DIGIT ++ * ++ * @param pd ParsedDirective ++ * ++ * @exception SaslException If an error occurs dur to too many charset ++ * directives or Invalid character encoding ++ * directive ++ */ ++ void handleCharset( ++ ParsedDirective pd) throws SaslException ++ { ++ if (null != m_characterSet) ++ throw new SaslException("Too many charset directives."); ++ ++ m_characterSet = pd.getValue(); ++ ++ if (!m_characterSet.equals("utf-8")) ++ throw new SaslException("Invalid character encoding directive"); ++ } ++ ++ /** ++ * This function implements the semenatics of the charset directive. ++ * the value is defined as: 1*DIGIT ++ * ++ * @param pd ParsedDirective ++ * ++ * @exception SaslException If an error occurs due to too many algorith ++ * directive or Invalid algorithm directive ++ * value ++ */ ++ void handleAlgorithm( ++ ParsedDirective pd) throws SaslException ++ { ++ if (null != m_algorithm) ++ throw new SaslException("Too many algorithm directives."); ++ ++ m_algorithm = pd.getValue(); ++ ++ if (!"md5-sess".equals(m_algorithm)) ++ throw new SaslException("Invalid algorithm directive value: " + ++ m_algorithm); ++ } ++ ++ /** ++ * This function implements the semenatics of the cipher-opts directive ++ * directive. The value of the qop directive is as defined below: ++ * qop-options = "qop" "=" <"> qop-list <"> ++ * qop-list = 1#qop-value ++ * qop-value = "auth" | "auth-int" | "auth-conf" | token ++ * ++ * @param pd ParsedDirective ++ * ++ * @exception SaslException If an error occurs due to Too many cipher ++ * directives ++ */ ++ void handleCipher( ++ ParsedDirective pd) throws SaslException ++ { ++ String token; ++ TokenParser parser; ++ ++ if (0 != m_cipherOptions) ++ throw new SaslException("Too many cipher directives."); ++ ++ parser = new TokenParser(pd.getValue()); ++ token = parser.parseToken(); ++ for (token = parser.parseToken(); ++ token != null; ++ token = parser.parseToken()) ++ { ++ if ("3des".equals(token)) ++ m_cipherOptions |= CIPHER_3DES; ++ else if ("des".equals(token)) ++ m_cipherOptions |= CIPHER_DES; ++ else if ("rc4-40".equals(token)) ++ m_cipherOptions |= CIPHER_RC4_40; ++ else if ("rc4".equals(token)) ++ m_cipherOptions |= CIPHER_RC4; ++ else if ("rc4-56".equals(token)) ++ m_cipherOptions |= CIPHER_RC4_56; ++ else ++ m_cipherOptions |= CIPHER_UNRECOGNIZED; ++ } ++ ++ if (m_cipherOptions == 0) ++ m_cipherOptions = CIPHER_UNRECOGNIZED; ++ } ++ ++ /** ++ * This function implements the semenatics of the stale directive. ++ * ++ * @param pd ParsedDirective ++ * ++ * @exception SaslException If an error occurs due to Too many stale ++ * directives or Invalid stale directive value ++ */ ++ void handleStale( ++ ParsedDirective pd) throws SaslException ++ { ++ if (false != m_staleFlag) ++ throw new SaslException("Too many stale directives."); ++ ++ if ("true".equals(pd.getValue())) ++ m_staleFlag = true; ++ else ++ throw new SaslException("Invalid stale directive value: " + ++ pd.getValue()); ++ } ++ ++ /** ++ * Return the list of the All the Realms ++ * ++ * @return List of all the realms ++ */ ++ public ArrayList getRealms() ++ { ++ return m_realms; ++ } ++ ++ /** ++ * @return Returns the Nonce ++ */ ++ public String getNonce() ++ { ++ return m_nonce; ++ } ++ ++ /** ++ * Return the quality-of-protection ++ * ++ * @return The quality-of-protection ++ */ ++ public int getQop() ++ { ++ return m_qop; ++ } ++ ++ /** ++ * @return The state of the Staleflag ++ */ ++ public boolean getStaleFlag() ++ { ++ return m_staleFlag; ++ } ++ ++ /** ++ * @return The Maximum Buffer value ++ */ ++ public int getMaxBuf() ++ { ++ return m_maxBuf; ++ } ++ ++ /** ++ * @return character set values as string ++ */ ++ public String getCharacterSet() ++ { ++ return m_characterSet; ++ } ++ ++ /** ++ * @return The String value of the algorithm ++ */ ++ public String getAlgorithm() ++ { ++ return m_algorithm; ++ } ++ ++ /** ++ * @return The cipher options ++ */ ++ public int getCipherOptions() ++ { ++ return m_cipherOptions; ++ } ++} ++ +diff --git a/external/asmack/build/src/trunk/com/novell/sasl/client/DigestMD5SaslClient.java b/external/asmack/build/src/trunk/com/novell/sasl/client/DigestMD5SaslClient.java +new file mode 100644 +index 0000000..141c96b +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/novell/sasl/client/DigestMD5SaslClient.java +@@ -0,0 +1,820 @@ ++/* ************************************************************************** ++ * $OpenLDAP: /com/novell/sasl/client/DigestMD5SaslClient.java,v 1.4 2005/01/17 15:00:54 sunilk Exp $ ++ * ++ * Copyright (C) 2003 Novell, Inc. All Rights Reserved. ++ * ++ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND ++ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT ++ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS ++ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE" ++ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION ++ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP ++ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT ++ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY. ++ ******************************************************************************/ ++package com.novell.sasl.client; ++ ++import org.apache.harmony.javax.security.sasl.*; ++import org.apache.harmony.javax.security.auth.callback.*; ++import java.security.SecureRandom; ++import java.security.MessageDigest; ++import java.security.NoSuchAlgorithmException; ++import java.io.UnsupportedEncodingException; ++import java.io.IOException; ++import java.util.*; ++ ++/** ++ * Implements the Client portion of DigestMD5 Sasl mechanism. ++ */ ++public class DigestMD5SaslClient implements SaslClient ++{ ++ private String m_authorizationId = ""; ++ private String m_protocol = ""; ++ private String m_serverName = ""; ++ private Map m_props; ++ private CallbackHandler m_cbh; ++ private int m_state; ++ private String m_qopValue = ""; ++ private char[] m_HA1 = null; ++ private String m_digestURI; ++ private DigestChallenge m_dc; ++ private String m_clientNonce = ""; ++ private String m_realm = ""; ++ private String m_name = ""; ++ ++ private static final int STATE_INITIAL = 0; ++ private static final int STATE_DIGEST_RESPONSE_SENT = 1; ++ private static final int STATE_VALID_SERVER_RESPONSE = 2; ++ private static final int STATE_INVALID_SERVER_RESPONSE = 3; ++ private static final int STATE_DISPOSED = 4; ++ ++ private static final int NONCE_BYTE_COUNT = 32; ++ private static final int NONCE_HEX_COUNT = 2*NONCE_BYTE_COUNT; ++ ++ private static final String DIGEST_METHOD = "AUTHENTICATE"; ++ ++ /** ++ * Creates an DigestMD5SaslClient object using the parameters supplied. ++ * Assumes that the QOP, STRENGTH, and SERVER_AUTH properties are ++ * contained in props ++ * ++ * @param authorizationId The possibly null protocol-dependent ++ * identification to be used for authorization. If ++ * null or empty, the server derives an authorization ++ * ID from the client's authentication credentials. ++ * When the SASL authentication completes ++ * successfully, the specified entity is granted ++ * access. ++ * ++ * @param protocol The non-null string name of the protocol for which ++ * the authentication is being performed (e.g. "ldap") ++ * ++ * @param serverName The non-null fully qualified host name of the server ++ * to authenticate to ++ * ++ * @param props The possibly null set of properties used to select ++ * the SASL mechanism and to configure the ++ * authentication exchange of the selected mechanism. ++ * See the Sasl class for a list of standard properties. ++ * Other, possibly mechanism-specific, properties can ++ * be included. Properties not relevant to the selected ++ * mechanism are ignored. ++ * ++ * @param cbh The possibly null callback handler to used by the ++ * SASL mechanisms to get further information from the ++ * application/library to complete the authentication. ++ * For example, a SASL mechanism might require the ++ * authentication ID, password and realm from the ++ * caller. The authentication ID is requested by using ++ * a NameCallback. The password is requested by using ++ * a PasswordCallback. The realm is requested by using ++ * a RealmChoiceCallback if there is a list of realms ++ * to choose from, and by using a RealmCallback if the ++ * realm must be entered. ++ * ++ * @return A possibly null SaslClient created using the ++ * parameters supplied. If null, this factory cannot ++ * produce a SaslClient using the parameters supplied. ++ * ++ * @exception SaslException If a SaslClient instance cannot be created ++ * because of an error ++ */ ++ public static SaslClient getClient( ++ String authorizationId, ++ String protocol, ++ String serverName, ++ Map props, ++ CallbackHandler cbh) ++ { ++ String desiredQOP = (String)props.get(Sasl.QOP); ++ String desiredStrength = (String)props.get(Sasl.STRENGTH); ++ String serverAuth = (String)props.get(Sasl.SERVER_AUTH); ++ ++ //only support qop equal to auth ++ if ((desiredQOP != null) && !"auth".equals(desiredQOP)) ++ return null; ++ ++ //doesn't support server authentication ++ if ((serverAuth != null) && !"false".equals(serverAuth)) ++ return null; ++ ++ //need a callback handler to get the password ++ if (cbh == null) ++ return null; ++ ++ return new DigestMD5SaslClient(authorizationId, protocol, ++ serverName, props, cbh); ++ } ++ ++ /** ++ * Creates an DigestMD5SaslClient object using the parameters supplied. ++ * Assumes that the QOP, STRENGTH, and SERVER_AUTH properties are ++ * contained in props ++ * ++ * @param authorizationId The possibly null protocol-dependent ++ * identification to be used for authorization. If ++ * null or empty, the server derives an authorization ++ * ID from the client's authentication credentials. ++ * When the SASL authentication completes ++ * successfully, the specified entity is granted ++ * access. ++ * ++ * @param protocol The non-null string name of the protocol for which ++ * the authentication is being performed (e.g. "ldap") ++ * ++ * @param serverName The non-null fully qualified host name of the server ++ * to authenticate to ++ * ++ * @param props The possibly null set of properties used to select ++ * the SASL mechanism and to configure the ++ * authentication exchange of the selected mechanism. ++ * See the Sasl class for a list of standard properties. ++ * Other, possibly mechanism-specific, properties can ++ * be included. Properties not relevant to the selected ++ * mechanism are ignored. ++ * ++ * @param cbh The possibly null callback handler to used by the ++ * SASL mechanisms to get further information from the ++ * application/library to complete the authentication. ++ * For example, a SASL mechanism might require the ++ * authentication ID, password and realm from the ++ * caller. The authentication ID is requested by using ++ * a NameCallback. The password is requested by using ++ * a PasswordCallback. The realm is requested by using ++ * a RealmChoiceCallback if there is a list of realms ++ * to choose from, and by using a RealmCallback if the ++ * realm must be entered. ++ * ++ */ ++ private DigestMD5SaslClient( ++ String authorizationId, ++ String protocol, ++ String serverName, ++ Map props, ++ CallbackHandler cbh) ++ { ++ m_authorizationId = authorizationId; ++ m_protocol = protocol; ++ m_serverName = serverName; ++ m_props = props; ++ m_cbh = cbh; ++ ++ m_state = STATE_INITIAL; ++ } ++ ++ /** ++ * Determines if this mechanism has an optional initial response. If true, ++ * caller should call evaluateChallenge() with an empty array to get the ++ * initial response. ++ * ++ * @return true if this mechanism has an initial response ++ */ ++ public boolean hasInitialResponse() ++ { ++ return false; ++ } ++ ++ /** ++ * Determines if the authentication exchange has completed. This method ++ * may be called at any time, but typically, it will not be called until ++ * the caller has received indication from the server (in a protocol- ++ * specific manner) that the exchange has completed. ++ * ++ * @return true if the authentication exchange has completed; ++ * false otherwise. ++ */ ++ public boolean isComplete() ++ { ++ if ((m_state == STATE_VALID_SERVER_RESPONSE) || ++ (m_state == STATE_INVALID_SERVER_RESPONSE) || ++ (m_state == STATE_DISPOSED)) ++ return true; ++ else ++ return false; ++ } ++ ++ /** ++ * Unwraps a byte array received from the server. This method can be called ++ * only after the authentication exchange has completed (i.e., when ++ * isComplete() returns true) and only if the authentication exchange has ++ * negotiated integrity and/or privacy as the quality of protection; ++ * otherwise, an IllegalStateException is thrown. ++ * ++ * incoming is the contents of the SASL buffer as defined in RFC 2222 ++ * without the leading four octet field that represents the length. ++ * offset and len specify the portion of incoming to use. ++ * ++ * @param incoming A non-null byte array containing the encoded bytes ++ * from the server ++ * @param offset The starting position at incoming of the bytes to use ++ * ++ * @param len The number of bytes from incoming to use ++ * ++ * @return A non-null byte array containing the decoded bytes ++ * ++ */ ++ public byte[] unwrap( ++ byte[] incoming, ++ int offset, ++ int len) ++ throws SaslException ++ { ++ throw new IllegalStateException( ++ "unwrap: QOP has neither integrity nor privacy>"); ++ } ++ ++ /** ++ * Wraps a byte array to be sent to the server. This method can be called ++ * only after the authentication exchange has completed (i.e., when ++ * isComplete() returns true) and only if the authentication exchange has ++ * negotiated integrity and/or privacy as the quality of protection; ++ * otherwise, an IllegalStateException is thrown. ++ * ++ * The result of this method will make up the contents of the SASL buffer as ++ * defined in RFC 2222 without the leading four octet field that represents ++ * the length. offset and len specify the portion of outgoing to use. ++ * ++ * @param outgoing A non-null byte array containing the bytes to encode ++ * @param offset The starting position at outgoing of the bytes to use ++ * @param len The number of bytes from outgoing to use ++ * ++ * @return A non-null byte array containing the encoded bytes ++ * ++ * @exception SaslException if incoming cannot be successfully unwrapped. ++ * ++ * @exception IllegalStateException if the authentication exchange has ++ * not completed, or if the negotiated quality of ++ * protection has neither integrity nor privacy. ++ */ ++ public byte[] wrap( ++ byte[] outgoing, ++ int offset, ++ int len) ++ throws SaslException ++ { ++ throw new IllegalStateException( ++ "wrap: QOP has neither integrity nor privacy>"); ++ } ++ ++ /** ++ * Retrieves the negotiated property. This method can be called only after ++ * the authentication exchange has completed (i.e., when isComplete() ++ * returns true); otherwise, an IllegalStateException is thrown. ++ * ++ * @param propName The non-null property name ++ * ++ * @return The value of the negotiated property. If null, the property was ++ * not negotiated or is not applicable to this mechanism. ++ * ++ * @exception IllegalStateException if this authentication exchange has ++ * not completed ++ */ ++ public Object getNegotiatedProperty( ++ String propName) ++ { ++ if (m_state != STATE_VALID_SERVER_RESPONSE) ++ throw new IllegalStateException( ++ "getNegotiatedProperty: authentication exchange not complete."); ++ ++ if (Sasl.QOP.equals(propName)) ++ return "auth"; ++ else ++ return null; ++ } ++ ++ /** ++ * Disposes of any system resources or security-sensitive information the ++ * SaslClient might be using. Invoking this method invalidates the ++ * SaslClient instance. This method is idempotent. ++ * ++ * @exception SaslException if a problem was encountered while disposing ++ * of the resources ++ */ ++ public void dispose() ++ throws SaslException ++ { ++ if (m_state != STATE_DISPOSED) ++ { ++ m_state = STATE_DISPOSED; ++ } ++ } ++ ++ /** ++ * Evaluates the challenge data and generates a response. If a challenge ++ * is received from the server during the authentication process, this ++ * method is called to prepare an appropriate next response to submit to ++ * the server. ++ * ++ * @param challenge The non-null challenge sent from the server. The ++ * challenge array may have zero length. ++ * ++ * @return The possibly null reponse to send to the server. It is null ++ * if the challenge accompanied a "SUCCESS" status and the ++ * challenge only contains data for the client to update its ++ * state and no response needs to be sent to the server. ++ * The response is a zero-length byte array if the client is to ++ * send a response with no data. ++ * ++ * @exception SaslException If an error occurred while processing the ++ * challenge or generating a response. ++ */ ++ public byte[] evaluateChallenge( ++ byte[] challenge) ++ throws SaslException ++ { ++ byte[] response = null; ++ ++ //printState(); ++ switch (m_state) ++ { ++ case STATE_INITIAL: ++ if (challenge.length == 0) ++ throw new SaslException("response = byte[0]"); ++ else ++ try ++ { ++ response = createDigestResponse(challenge). ++ getBytes("UTF-8"); ++ m_state = STATE_DIGEST_RESPONSE_SENT; ++ } ++ catch (java.io.UnsupportedEncodingException e) ++ { ++ throw new SaslException( ++ "UTF-8 encoding not suppported by platform", e); ++ } ++ break; ++ case STATE_DIGEST_RESPONSE_SENT: ++ if (checkServerResponseAuth(challenge)) ++ m_state = STATE_VALID_SERVER_RESPONSE; ++ else ++ { ++ m_state = STATE_INVALID_SERVER_RESPONSE; ++ throw new SaslException("Could not validate response-auth " + ++ "value from server"); ++ } ++ break; ++ case STATE_VALID_SERVER_RESPONSE: ++ case STATE_INVALID_SERVER_RESPONSE: ++ throw new SaslException("Authentication sequence is complete"); ++ case STATE_DISPOSED: ++ throw new SaslException("Client has been disposed"); ++ default: ++ throw new SaslException("Unknown client state."); ++ } ++ ++ return response; ++ } ++ ++ /** ++ * This function takes a 16 byte binary md5-hash value and creates a 32 ++ * character (plus a terminating null character) hex-digit ++ * representation of binary data. ++ * ++ * @param hash 16 byte binary md5-hash value in bytes ++ * ++ * @return 32 character (plus a terminating null character) hex-digit ++ * representation of binary data. ++ */ ++ char[] convertToHex( ++ byte[] hash) ++ { ++ int i; ++ byte j; ++ byte fifteen = 15; ++ char[] hex = new char[32]; ++ ++ for (i = 0; i < 16; i++) ++ { ++ //convert value of top 4 bits to hex char ++ hex[i*2] = getHexChar((byte)((hash[i] & 0xf0) >> 4)); ++ //convert value of bottom 4 bits to hex char ++ hex[(i*2)+1] = getHexChar((byte)(hash[i] & 0x0f)); ++ } ++ ++ return hex; ++ } ++ ++ /** ++ * Calculates the HA1 portion of the response ++ * ++ * @param algorithm Algorith to use. ++ * @param userName User being authenticated ++ * @param realm realm information ++ * @param password password of teh user ++ * @param nonce nonce value ++ * @param clientNonce Clients Nonce value ++ * ++ * @return HA1 portion of the response in a character array ++ * ++ * @exception SaslException If an error occurs ++ */ ++ char[] DigestCalcHA1( ++ String algorithm, ++ String userName, ++ String realm, ++ String password, ++ String nonce, ++ String clientNonce) throws SaslException ++ { ++ byte[] hash; ++ ++ try ++ { ++ MessageDigest md = MessageDigest.getInstance("MD5"); ++ ++ md.update(userName.getBytes("UTF-8")); ++ md.update(":".getBytes("UTF-8")); ++ md.update(realm.getBytes("UTF-8")); ++ md.update(":".getBytes("UTF-8")); ++ md.update(password.getBytes("UTF-8")); ++ hash = md.digest(); ++ ++ if ("md5-sess".equals(algorithm)) ++ { ++ md.update(hash); ++ md.update(":".getBytes("UTF-8")); ++ md.update(nonce.getBytes("UTF-8")); ++ md.update(":".getBytes("UTF-8")); ++ md.update(clientNonce.getBytes("UTF-8")); ++ hash = md.digest(); ++ } ++ } ++ catch(NoSuchAlgorithmException e) ++ { ++ throw new SaslException("No provider found for MD5 hash", e); ++ } ++ catch(UnsupportedEncodingException e) ++ { ++ throw new SaslException( ++ "UTF-8 encoding not supported by platform.", e); ++ } ++ ++ return convertToHex(hash); ++ } ++ ++ ++ /** ++ * This function calculates the response-value of the response directive of ++ * the digest-response as documented in RFC 2831 ++ * ++ * @param HA1 H(A1) ++ * @param serverNonce nonce from server ++ * @param nonceCount 8 hex digits ++ * @param clientNonce client nonce ++ * @param qop qop-value: "", "auth", "auth-int" ++ * @param method method from the request ++ * @param digestUri requested URL ++ * @param clientResponseFlag request-digest or response-digest ++ * ++ * @return Response-value of the response directive of the digest-response ++ * ++ * @exception SaslException If an error occurs ++ */ ++ char[] DigestCalcResponse( ++ char[] HA1, /* H(A1) */ ++ String serverNonce, /* nonce from server */ ++ String nonceCount, /* 8 hex digits */ ++ String clientNonce, /* client nonce */ ++ String qop, /* qop-value: "", "auth", "auth-int" */ ++ String method, /* method from the request */ ++ String digestUri, /* requested URL */ ++ boolean clientResponseFlag) /* request-digest or response-digest */ ++ throws SaslException ++ { ++ byte[] HA2; ++ byte[] respHash; ++ char[] HA2Hex; ++ ++ // calculate H(A2) ++ try ++ { ++ MessageDigest md = MessageDigest.getInstance("MD5"); ++ if (clientResponseFlag) ++ md.update(method.getBytes("UTF-8")); ++ md.update(":".getBytes("UTF-8")); ++ md.update(digestUri.getBytes("UTF-8")); ++ if ("auth-int".equals(qop)) ++ { ++ md.update(":".getBytes("UTF-8")); ++ md.update("00000000000000000000000000000000".getBytes("UTF-8")); ++ } ++ HA2 = md.digest(); ++ HA2Hex = convertToHex(HA2); ++ ++ // calculate response ++ md.update(new String(HA1).getBytes("UTF-8")); ++ md.update(":".getBytes("UTF-8")); ++ md.update(serverNonce.getBytes("UTF-8")); ++ md.update(":".getBytes("UTF-8")); ++ if (qop.length() > 0) ++ { ++ md.update(nonceCount.getBytes("UTF-8")); ++ md.update(":".getBytes("UTF-8")); ++ md.update(clientNonce.getBytes("UTF-8")); ++ md.update(":".getBytes("UTF-8")); ++ md.update(qop.getBytes("UTF-8")); ++ md.update(":".getBytes("UTF-8")); ++ } ++ md.update(new String(HA2Hex).getBytes("UTF-8")); ++ respHash = md.digest(); ++ } ++ catch(NoSuchAlgorithmException e) ++ { ++ throw new SaslException("No provider found for MD5 hash", e); ++ } ++ catch(UnsupportedEncodingException e) ++ { ++ throw new SaslException( ++ "UTF-8 encoding not supported by platform.", e); ++ } ++ ++ return convertToHex(respHash); ++ } ++ ++ ++ /** ++ * Creates the intial response to be sent to the server. ++ * ++ * @param challenge Challenge in bytes recived form the Server ++ * ++ * @return Initial response to be sent to the server ++ */ ++ private String createDigestResponse( ++ byte[] challenge) ++ throws SaslException ++ { ++ char[] response; ++ StringBuffer digestResponse = new StringBuffer(512); ++ int realmSize; ++ ++ m_dc = new DigestChallenge(challenge); ++ ++ m_digestURI = m_protocol + "/" + m_serverName; ++ ++ if ((m_dc.getQop() & DigestChallenge.QOP_AUTH) ++ == DigestChallenge.QOP_AUTH ) ++ m_qopValue = "auth"; ++ else ++ throw new SaslException("Client only supports qop of 'auth'"); ++ ++ //get call back information ++ Callback[] callbacks = new Callback[3]; ++ ArrayList realms = m_dc.getRealms(); ++ realmSize = realms.size(); ++ if (realmSize == 0) ++ { ++ callbacks[0] = new RealmCallback("Realm"); ++ } ++ else if (realmSize == 1) ++ { ++ callbacks[0] = new RealmCallback("Realm", (String)realms.get(0)); ++ } ++ else ++ { ++ callbacks[0] = ++ new RealmChoiceCallback( ++ "Realm", ++ (String[])realms.toArray(new String[realmSize]), ++ 0, //the default choice index ++ false); //no multiple selections ++ } ++ ++ callbacks[1] = new PasswordCallback("Password", false); ++ //false = no echo ++ ++ if (m_authorizationId == null || m_authorizationId.length() == 0) ++ callbacks[2] = new NameCallback("Name"); ++ else ++ callbacks[2] = new NameCallback("Name", m_authorizationId); ++ ++ try ++ { ++ m_cbh.handle(callbacks); ++ } ++ catch(UnsupportedCallbackException e) ++ { ++ throw new SaslException("Handler does not support" + ++ " necessary callbacks",e); ++ } ++ catch(IOException e) ++ { ++ throw new SaslException("IO exception in CallbackHandler.", e); ++ } ++ ++ if (realmSize > 1) ++ { ++ int[] selections = ++ ((RealmChoiceCallback)callbacks[0]).getSelectedIndexes(); ++ ++ if (selections.length > 0) ++ m_realm = ++ ((RealmChoiceCallback)callbacks[0]).getChoices()[selections[0]]; ++ else ++ m_realm = ((RealmChoiceCallback)callbacks[0]).getChoices()[0]; ++ } ++ else ++ m_realm = ((RealmCallback)callbacks[0]).getText(); ++ ++ m_clientNonce = getClientNonce(); ++ ++ m_name = ((NameCallback)callbacks[2]).getName(); ++ if (m_name == null) ++ m_name = ((NameCallback)callbacks[2]).getDefaultName(); ++ if (m_name == null) ++ throw new SaslException("No user name was specified."); ++ ++ m_HA1 = DigestCalcHA1( ++ m_dc.getAlgorithm(), ++ m_name, ++ m_realm, ++ new String(((PasswordCallback)callbacks[1]).getPassword()), ++ m_dc.getNonce(), ++ m_clientNonce); ++ ++ response = DigestCalcResponse(m_HA1, ++ m_dc.getNonce(), ++ "00000001", ++ m_clientNonce, ++ m_qopValue, ++ "AUTHENTICATE", ++ m_digestURI, ++ true); ++ ++ digestResponse.append("username=\""); ++ digestResponse.append(m_authorizationId); ++ if (0 != m_realm.length()) ++ { ++ digestResponse.append("\",realm=\""); ++ digestResponse.append(m_realm); ++ } ++ digestResponse.append("\",cnonce=\""); ++ digestResponse.append(m_clientNonce); ++ digestResponse.append("\",nc="); ++ digestResponse.append("00000001"); //nounce count ++ digestResponse.append(",qop="); ++ digestResponse.append(m_qopValue); ++ digestResponse.append(",digest-uri=\""); ++ digestResponse.append(m_digestURI); ++ digestResponse.append("\",response="); ++ digestResponse.append(response); ++ digestResponse.append(",charset=utf-8,nonce=\""); ++ digestResponse.append(m_dc.getNonce()); ++ digestResponse.append("\""); ++ ++ return digestResponse.toString(); ++ } ++ ++ ++ /** ++ * This function validates the server response. This step performs a ++ * modicum of mutual authentication by verifying that the server knows ++ * the user's password ++ * ++ * @param serverResponse Response recived form Server ++ * ++ * @return true if the mutual authentication succeeds; ++ * else return false ++ * ++ * @exception SaslException If an error occurs ++ */ ++ boolean checkServerResponseAuth( ++ byte[] serverResponse) throws SaslException ++ { ++ char[] response; ++ ResponseAuth responseAuth = null; ++ String responseStr; ++ ++ responseAuth = new ResponseAuth(serverResponse); ++ ++ response = DigestCalcResponse(m_HA1, ++ m_dc.getNonce(), ++ "00000001", ++ m_clientNonce, ++ m_qopValue, ++ DIGEST_METHOD, ++ m_digestURI, ++ false); ++ ++ responseStr = new String(response); ++ ++ return responseStr.equals(responseAuth.getResponseValue()); ++ } ++ ++ ++ /** ++ * This function returns hex character representing the value of the input ++ * ++ * @param value Input value in byte ++ * ++ * @return Hex value of the Input byte value ++ */ ++ private static char getHexChar( ++ byte value) ++ { ++ switch (value) ++ { ++ case 0: ++ return '0'; ++ case 1: ++ return '1'; ++ case 2: ++ return '2'; ++ case 3: ++ return '3'; ++ case 4: ++ return '4'; ++ case 5: ++ return '5'; ++ case 6: ++ return '6'; ++ case 7: ++ return '7'; ++ case 8: ++ return '8'; ++ case 9: ++ return '9'; ++ case 10: ++ return 'a'; ++ case 11: ++ return 'b'; ++ case 12: ++ return 'c'; ++ case 13: ++ return 'd'; ++ case 14: ++ return 'e'; ++ case 15: ++ return 'f'; ++ default: ++ return 'Z'; ++ } ++ } ++ ++ /** ++ * Calculates the Nonce value of the Client ++ * ++ * @return Nonce value of the client ++ * ++ * @exception SaslException If an error Occurs ++ */ ++ String getClientNonce() throws SaslException ++ { ++ byte[] nonceBytes = new byte[NONCE_BYTE_COUNT]; ++ SecureRandom prng; ++ byte nonceByte; ++ char[] hexNonce = new char[NONCE_HEX_COUNT]; ++ ++ try ++ { ++ prng = SecureRandom.getInstance("SHA1PRNG"); ++ prng.nextBytes(nonceBytes); ++ for(int i=0; i> 4)); ++ } ++ return new String(hexNonce); ++ } ++ catch(NoSuchAlgorithmException e) ++ { ++ throw new SaslException("No random number generator available", e); ++ } ++ } ++ ++ /** ++ * Returns the IANA-registered mechanism name of this SASL client. ++ * (e.g. "CRAM-MD5", "GSSAPI") ++ * ++ * @return "DIGEST-MD5"the IANA-registered mechanism name of this SASL ++ * client. ++ */ ++ public String getMechanismName() ++ { ++ return "DIGEST-MD5"; ++ } ++ ++} //end class DigestMD5SaslClient ++ +diff --git a/external/asmack/build/src/trunk/com/novell/sasl/client/DirectiveList.java b/external/asmack/build/src/trunk/com/novell/sasl/client/DirectiveList.java +new file mode 100644 +index 0000000..fc26a6b +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/novell/sasl/client/DirectiveList.java +@@ -0,0 +1,363 @@ ++/* ************************************************************************** ++ * $OpenLDAP: /com/novell/sasl/client/DirectiveList.java,v 1.4 2005/01/17 15:00:54 sunilk Exp $ ++ * ++ * Copyright (C) 2002 Novell, Inc. All Rights Reserved. ++ * ++ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND ++ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT ++ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS ++ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE" ++ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION ++ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP ++ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT ++ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY. ++ ******************************************************************************/ ++package com.novell.sasl.client; ++ ++import java.util.*; ++import org.apache.harmony.javax.security.sasl.*; ++import java.io.UnsupportedEncodingException; ++ ++/** ++ * Implements the DirectiveList class whihc will be used by the ++ * DigestMD5SaslClient class ++ */ ++class DirectiveList extends Object ++{ ++ private static final int STATE_LOOKING_FOR_FIRST_DIRECTIVE = 1; ++ private static final int STATE_LOOKING_FOR_DIRECTIVE = 2; ++ private static final int STATE_SCANNING_NAME = 3; ++ private static final int STATE_LOOKING_FOR_EQUALS = 4; ++ private static final int STATE_LOOKING_FOR_VALUE = 5; ++ private static final int STATE_LOOKING_FOR_COMMA = 6; ++ private static final int STATE_SCANNING_QUOTED_STRING_VALUE = 7; ++ private static final int STATE_SCANNING_TOKEN_VALUE = 8; ++ private static final int STATE_NO_UTF8_SUPPORT = 9; ++ ++ private int m_curPos; ++ private int m_errorPos; ++ private String m_directives; ++ private int m_state; ++ private ArrayList m_directiveList; ++ private String m_curName; ++ private int m_scanStart; ++ ++ /** ++ * Constructs a new DirectiveList. ++ */ ++ DirectiveList( ++ byte[] directives) ++ { ++ m_curPos = 0; ++ m_state = STATE_LOOKING_FOR_FIRST_DIRECTIVE; ++ m_directiveList = new ArrayList(10); ++ m_scanStart = 0; ++ m_errorPos = -1; ++ try ++ { ++ m_directives = new String(directives, "UTF-8"); ++ } ++ catch(UnsupportedEncodingException e) ++ { ++ m_state = STATE_NO_UTF8_SUPPORT; ++ } ++ } ++ ++ /** ++ * This function takes a US-ASCII character string containing a list of comma ++ * separated directives, and parses the string into the individual directives ++ * and their values. A directive consists of a token specifying the directive ++ * name followed by an equal sign (=) and the directive value. The value is ++ * either a token or a quoted string ++ * ++ * @exception SaslException If an error Occurs ++ */ ++ void parseDirectives() throws SaslException ++ { ++ char prevChar; ++ char currChar; ++ int rc = 0; ++ boolean haveQuotedPair = false; ++ String currentName = ""; ++ ++ if (m_state == STATE_NO_UTF8_SUPPORT) ++ throw new SaslException("No UTF-8 support on platform"); ++ ++ prevChar = 0; ++ ++ while (m_curPos < m_directives.length()) ++ { ++ currChar = m_directives.charAt(m_curPos); ++ switch (m_state) ++ { ++ case STATE_LOOKING_FOR_FIRST_DIRECTIVE: ++ case STATE_LOOKING_FOR_DIRECTIVE: ++ if (isWhiteSpace(currChar)) ++ { ++ break; ++ } ++ else if (isValidTokenChar(currChar)) ++ { ++ m_scanStart = m_curPos; ++ m_state = STATE_SCANNING_NAME; ++ } ++ else ++ { ++ m_errorPos = m_curPos; ++ throw new SaslException("Parse error: Invalid name character"); ++ } ++ break; ++ ++ case STATE_SCANNING_NAME: ++ if (isValidTokenChar(currChar)) ++ { ++ break; ++ } ++ else if (isWhiteSpace(currChar)) ++ { ++ currentName = m_directives.substring(m_scanStart, m_curPos); ++ m_state = STATE_LOOKING_FOR_EQUALS; ++ } ++ else if ('=' == currChar) ++ { ++ currentName = m_directives.substring(m_scanStart, m_curPos); ++ m_state = STATE_LOOKING_FOR_VALUE; ++ } ++ else ++ { ++ m_errorPos = m_curPos; ++ throw new SaslException("Parse error: Invalid name character"); ++ } ++ break; ++ ++ case STATE_LOOKING_FOR_EQUALS: ++ if (isWhiteSpace(currChar)) ++ { ++ break; ++ } ++ else if ('=' == currChar) ++ { ++ m_state = STATE_LOOKING_FOR_VALUE; ++ } ++ else ++ { ++ m_errorPos = m_curPos; ++ throw new SaslException("Parse error: Expected equals sign '='."); ++ } ++ break; ++ ++ case STATE_LOOKING_FOR_VALUE: ++ if (isWhiteSpace(currChar)) ++ { ++ break; ++ } ++ else if ('"' == currChar) ++ { ++ m_scanStart = m_curPos+1; /* don't include the quote */ ++ m_state = STATE_SCANNING_QUOTED_STRING_VALUE; ++ } ++ else if (isValidTokenChar(currChar)) ++ { ++ m_scanStart = m_curPos; ++ m_state = STATE_SCANNING_TOKEN_VALUE; ++ } ++ else ++ { ++ m_errorPos = m_curPos; ++ throw new SaslException("Parse error: Unexpected character"); ++ } ++ break; ++ ++ case STATE_SCANNING_TOKEN_VALUE: ++ if (isValidTokenChar(currChar)) ++ { ++ break; ++ } ++ else if (isWhiteSpace(currChar)) ++ { ++ addDirective(currentName, false); ++ m_state = STATE_LOOKING_FOR_COMMA; ++ } ++ else if (',' == currChar) ++ { ++ addDirective(currentName, false); ++ m_state = STATE_LOOKING_FOR_DIRECTIVE; ++ } ++ else ++ { ++ m_errorPos = m_curPos; ++ throw new SaslException("Parse error: Invalid value character"); ++ } ++ break; ++ ++ case STATE_SCANNING_QUOTED_STRING_VALUE: ++ if ('\\' == currChar) ++ haveQuotedPair = true; ++ if ( ('"' == currChar) && ++ ('\\' != prevChar) ) ++ { ++ addDirective(currentName, haveQuotedPair); ++ haveQuotedPair = false; ++ m_state = STATE_LOOKING_FOR_COMMA; ++ } ++ break; ++ ++ case STATE_LOOKING_FOR_COMMA: ++ if (isWhiteSpace(currChar)) ++ break; ++ else if (currChar == ',') ++ m_state = STATE_LOOKING_FOR_DIRECTIVE; ++ else ++ { ++ m_errorPos = m_curPos; ++ throw new SaslException("Parse error: Expected a comma."); ++ } ++ break; ++ } ++ if (0 != rc) ++ break; ++ prevChar = currChar; ++ m_curPos++; ++ } /* end while loop */ ++ ++ ++ if (rc == 0) ++ { ++ /* check the ending state */ ++ switch (m_state) ++ { ++ case STATE_SCANNING_TOKEN_VALUE: ++ addDirective(currentName, false); ++ break; ++ ++ case STATE_LOOKING_FOR_FIRST_DIRECTIVE: ++ case STATE_LOOKING_FOR_COMMA: ++ break; ++ ++ case STATE_LOOKING_FOR_DIRECTIVE: ++ throw new SaslException("Parse error: Trailing comma."); ++ ++ case STATE_SCANNING_NAME: ++ case STATE_LOOKING_FOR_EQUALS: ++ case STATE_LOOKING_FOR_VALUE: ++ throw new SaslException("Parse error: Missing value."); ++ ++ case STATE_SCANNING_QUOTED_STRING_VALUE: ++ throw new SaslException("Parse error: Missing closing quote."); ++ } ++ } ++ ++ } ++ ++ /** ++ * This function returns TRUE if the character is a valid token character. ++ * ++ * token = 1* ++ * ++ * separators = "(" | ")" | "<" | ">" | "@" ++ * | "," | ";" | ":" | "\" | <"> ++ * | "/" | "[" | "]" | "?" | "=" ++ * | "{" | "}" | SP | HT ++ * ++ * CTL = ++ * ++ * CHAR = ++ * ++ * @param c character to be tested ++ * ++ * @return Returns TRUE if the character is a valid token character. ++ */ ++ boolean isValidTokenChar( ++ char c) ++ { ++ if ( ( (c >= '\u0000') && (c <='\u0020') ) || ++ ( (c >= '\u003a') && (c <= '\u0040') ) || ++ ( (c >= '\u005b') && (c <= '\u005d') ) || ++ ('\u002c' == c) || ++ ('\u0025' == c) || ++ ('\u0028' == c) || ++ ('\u0029' == c) || ++ ('\u007b' == c) || ++ ('\u007d' == c) || ++ ('\u007f' == c) ) ++ return false; ++ ++ return true; ++ } ++ ++ /** ++ * This function returns TRUE if the character is linear white space (LWS). ++ * LWS = [CRLF] 1*( SP | HT ) ++ * @param c Input charcter to be tested ++ * ++ * @return Returns TRUE if the character is linear white space (LWS) ++ */ ++ boolean isWhiteSpace( ++ char c) ++ { ++ if ( ('\t' == c) || // HORIZONTAL TABULATION. ++ ('\n' == c) || // LINE FEED. ++ ('\r' == c) || // CARRIAGE RETURN. ++ ('\u0020' == c) ) ++ return true; ++ ++ return false; ++ } ++ ++ /** ++ * This function creates a directive record and adds it to the list, the ++ * value will be added later after it is parsed. ++ * ++ * @param name Name ++ * @param haveQuotedPair true if quoted pair is there else false ++ */ ++ void addDirective( ++ String name, ++ boolean haveQuotedPair) ++ { ++ String value; ++ int inputIndex; ++ int valueIndex; ++ char valueChar; ++ int type; ++ ++ if (!haveQuotedPair) ++ { ++ value = m_directives.substring(m_scanStart, m_curPos); ++ } ++ else ++ { //copy one character at a time skipping backslash excapes. ++ StringBuffer valueBuf = new StringBuffer(m_curPos - m_scanStart); ++ valueIndex = 0; ++ inputIndex = m_scanStart; ++ while (inputIndex < m_curPos) ++ { ++ if ('\\' == (valueChar = m_directives.charAt(inputIndex))) ++ inputIndex++; ++ valueBuf.setCharAt(valueIndex, m_directives.charAt(inputIndex)); ++ valueIndex++; ++ inputIndex++; ++ } ++ value = new String(valueBuf); ++ } ++ ++ if (m_state == STATE_SCANNING_QUOTED_STRING_VALUE) ++ type = ParsedDirective.QUOTED_STRING_VALUE; ++ else ++ type = ParsedDirective.TOKEN_VALUE; ++ m_directiveList.add(new ParsedDirective(name, value, type)); ++ } ++ ++ ++ /** ++ * Returns the List iterator. ++ * ++ * @return Returns the Iterator Object for the List. ++ */ ++ Iterator getIterator() ++ { ++ return m_directiveList.iterator(); ++ } ++} ++ +diff --git a/external/asmack/build/src/trunk/com/novell/sasl/client/ParsedDirective.java b/external/asmack/build/src/trunk/com/novell/sasl/client/ParsedDirective.java +new file mode 100644 +index 0000000..17bf70e +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/novell/sasl/client/ParsedDirective.java +@@ -0,0 +1,56 @@ ++/* ************************************************************************** ++ * $OpenLDAP: /com/novell/sasl/client/ParsedDirective.java,v 1.1 2003/08/21 10:06:26 kkanil Exp $ ++ * ++ * Copyright (C) 2002 Novell, Inc. All Rights Reserved. ++ * ++ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND ++ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT ++ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS ++ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE" ++ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION ++ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP ++ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT ++ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY. ++ ******************************************************************************/ ++package com.novell.sasl.client; ++ ++/** ++ * Implements the ParsedDirective class which will be used in the ++ * DigestMD5SaslClient mechanism. ++ */ ++class ParsedDirective ++{ ++ public static final int QUOTED_STRING_VALUE = 1; ++ public static final int TOKEN_VALUE = 2; ++ ++ private int m_valueType; ++ private String m_name; ++ private String m_value; ++ ++ ParsedDirective( ++ String name, ++ String value, ++ int type) ++ { ++ m_name = name; ++ m_value = value; ++ m_valueType = type; ++ } ++ ++ String getValue() ++ { ++ return m_value; ++ } ++ ++ String getName() ++ { ++ return m_name; ++ } ++ ++ int getValueType() ++ { ++ return m_valueType; ++ } ++ ++} ++ +diff --git a/external/asmack/build/src/trunk/com/novell/sasl/client/ResponseAuth.java b/external/asmack/build/src/trunk/com/novell/sasl/client/ResponseAuth.java +new file mode 100644 +index 0000000..0aef955 +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/novell/sasl/client/ResponseAuth.java +@@ -0,0 +1,83 @@ ++/* ************************************************************************** ++ * $OpenLDAP: /com/novell/sasl/client/ResponseAuth.java,v 1.3 2005/01/17 15:00:54 sunilk Exp $ ++ * ++ * Copyright (C) 2002 Novell, Inc. All Rights Reserved. ++ * ++ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND ++ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT ++ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS ++ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE" ++ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION ++ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP ++ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT ++ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY. ++ ******************************************************************************/ ++package com.novell.sasl.client; ++ ++import java.util.*; ++import org.apache.harmony.javax.security.sasl.*; ++ ++/** ++ * Implements the ResponseAuth class used by the DigestMD5SaslClient mechanism ++ */ ++class ResponseAuth ++{ ++ ++ private String m_responseValue; ++ ++ ResponseAuth( ++ byte[] responseAuth) ++ throws SaslException ++ { ++ m_responseValue = null; ++ ++ DirectiveList dirList = new DirectiveList(responseAuth); ++ try ++ { ++ dirList.parseDirectives(); ++ checkSemantics(dirList); ++ } ++ catch (SaslException e) ++ { ++ } ++ } ++ ++ /** ++ * Checks the semantics of the directives in the directive list as parsed ++ * from the digest challenge byte array. ++ * ++ * @param dirList the list of directives parsed from the digest challenge ++ * ++ * @exception SaslException If a semantic error occurs ++ */ ++ void checkSemantics( ++ DirectiveList dirList) throws SaslException ++ { ++ Iterator directives = dirList.getIterator(); ++ ParsedDirective directive; ++ String name; ++ ++ while (directives.hasNext()) ++ { ++ directive = (ParsedDirective)directives.next(); ++ name = directive.getName(); ++ if (name.equals("rspauth")) ++ m_responseValue = directive.getValue(); ++ } ++ ++ /* post semantic check */ ++ if (m_responseValue == null) ++ throw new SaslException("Missing response-auth directive."); ++ } ++ ++ /** ++ * returns the ResponseValue ++ * ++ * @return the ResponseValue as a String. ++ */ ++ public String getResponseValue() ++ { ++ return m_responseValue; ++ } ++} ++ +diff --git a/external/asmack/build/src/trunk/com/novell/sasl/client/TokenParser.java b/external/asmack/build/src/trunk/com/novell/sasl/client/TokenParser.java +new file mode 100644 +index 0000000..3d3491d +--- /dev/null ++++ b/external/asmack/build/src/trunk/com/novell/sasl/client/TokenParser.java +@@ -0,0 +1,208 @@ ++/* ************************************************************************** ++ * $OpenLDAP: /com/novell/sasl/client/TokenParser.java,v 1.3 2005/01/17 15:00:54 sunilk Exp $ ++ * ++ * Copyright (C) 2002 Novell, Inc. All Rights Reserved. ++ * ++ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND ++ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT ++ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS ++ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE" ++ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION ++ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP ++ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT ++ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY. ++ ******************************************************************************/ ++package com.novell.sasl.client; ++ ++import org.apache.harmony.javax.security.sasl.*; ++/** ++ * The TokenParser class will parse individual tokens from a list of tokens that ++ * are a directive value for a DigestMD5 authentication.The tokens are separated ++ * commas. ++ */ ++class TokenParser extends Object ++{ ++ private static final int STATE_LOOKING_FOR_FIRST_TOKEN = 1; ++ private static final int STATE_LOOKING_FOR_TOKEN = 2; ++ private static final int STATE_SCANNING_TOKEN = 3; ++ private static final int STATE_LOOKING_FOR_COMMA = 4; ++ private static final int STATE_PARSING_ERROR = 5; ++ private static final int STATE_DONE = 6; ++ ++ private int m_curPos; ++ private int m_scanStart; ++ private int m_state; ++ private String m_tokens; ++ ++ ++ TokenParser( ++ String tokens) ++ { ++ m_tokens = tokens; ++ m_curPos = 0; ++ m_scanStart = 0; ++ m_state = STATE_LOOKING_FOR_FIRST_TOKEN; ++ } ++ ++ /** ++ * This function parses the next token from the tokens string and returns ++ * it as a string. If there are no more tokens a null reference is returned. ++ * ++ * @return the parsed token or a null reference if there are no more ++ * tokens ++ * ++ * @exception SASLException if an error occurs while parsing ++ */ ++ String parseToken() throws SaslException ++ { ++ char currChar; ++ String token = null; ++ ++ ++ if (m_state == STATE_DONE) ++ return null; ++ ++ while (m_curPos < m_tokens.length() && (token == null)) ++ { ++ currChar = m_tokens.charAt(m_curPos); ++ switch (m_state) ++ { ++ case STATE_LOOKING_FOR_FIRST_TOKEN: ++ case STATE_LOOKING_FOR_TOKEN: ++ if (isWhiteSpace(currChar)) ++ { ++ break; ++ } ++ else if (isValidTokenChar(currChar)) ++ { ++ m_scanStart = m_curPos; ++ m_state = STATE_SCANNING_TOKEN; ++ } ++ else ++ { ++ m_state = STATE_PARSING_ERROR; ++ throw new SaslException("Invalid token character at position " + m_curPos); ++ } ++ break; ++ ++ case STATE_SCANNING_TOKEN: ++ if (isValidTokenChar(currChar)) ++ { ++ break; ++ } ++ else if (isWhiteSpace(currChar)) ++ { ++ token = m_tokens.substring(m_scanStart, m_curPos); ++ m_state = STATE_LOOKING_FOR_COMMA; ++ } ++ else if (',' == currChar) ++ { ++ token = m_tokens.substring(m_scanStart, m_curPos); ++ m_state = STATE_LOOKING_FOR_TOKEN; ++ } ++ else ++ { ++ m_state = STATE_PARSING_ERROR; ++ throw new SaslException("Invalid token character at position " + m_curPos); ++ } ++ break; ++ ++ ++ case STATE_LOOKING_FOR_COMMA: ++ if (isWhiteSpace(currChar)) ++ break; ++ else if (currChar == ',') ++ m_state = STATE_LOOKING_FOR_TOKEN; ++ else ++ { ++ m_state = STATE_PARSING_ERROR; ++ throw new SaslException("Expected a comma, found '" + ++ currChar + "' at postion " + ++ m_curPos); ++ } ++ break; ++ } ++ m_curPos++; ++ } /* end while loop */ ++ ++ if (token == null) ++ { /* check the ending state */ ++ switch (m_state) ++ { ++ case STATE_SCANNING_TOKEN: ++ token = m_tokens.substring(m_scanStart); ++ m_state = STATE_DONE; ++ break; ++ ++ case STATE_LOOKING_FOR_FIRST_TOKEN: ++ case STATE_LOOKING_FOR_COMMA: ++ break; ++ ++ case STATE_LOOKING_FOR_TOKEN: ++ throw new SaslException("Trialing comma"); ++ } ++ } ++ ++ return token; ++ } ++ ++ /** ++ * This function returns TRUE if the character is a valid token character. ++ * ++ * token = 1* ++ * ++ * separators = "(" | ")" | "<" | ">" | "@" ++ * | "," | ";" | ":" | "\" | <"> ++ * | "/" | "[" | "]" | "?" | "=" ++ * | "{" | "}" | SP | HT ++ * ++ * CTL = ++ * ++ * CHAR = ++ * ++ * @param c character to be validated ++ * ++ * @return True if character is valid Token character else it returns ++ * false ++ */ ++ boolean isValidTokenChar( ++ char c) ++ { ++ if ( ( (c >= '\u0000') && (c <='\u0020') ) || ++ ( (c >= '\u003a') && (c <= '\u0040') ) || ++ ( (c >= '\u005b') && (c <= '\u005d') ) || ++ ('\u002c' == c) || ++ ('\u0025' == c) || ++ ('\u0028' == c) || ++ ('\u0029' == c) || ++ ('\u007b' == c) || ++ ('\u007d' == c) || ++ ('\u007f' == c) ) ++ return false; ++ ++ return true; ++ } ++ ++ /** ++ * This function returns TRUE if the character is linear white space (LWS). ++ * LWS = [CRLF] 1*( SP | HT ) ++ * ++ * @param c character to be validated ++ * ++ * @return True if character is liner whitespace else it returns false ++ */ ++ boolean isWhiteSpace( ++ char c) ++ { ++ if ( ('\t' == c) || // HORIZONTAL TABULATION. ++ ('\n' == c) || // LINE FEED. ++ ('\r' == c) || // CARRIAGE RETURN. ++ ('\u0020' == c) ) ++ return true; ++ ++ return false; ++ } ++ ++} ++ +diff --git a/external/asmack/build/src/trunk/de/measite/smack/AndroidDebugger.java b/external/asmack/build/src/trunk/de/measite/smack/AndroidDebugger.java +new file mode 100644 +index 0000000..4dfc622 +--- /dev/null ++++ b/external/asmack/build/src/trunk/de/measite/smack/AndroidDebugger.java +@@ -0,0 +1,185 @@ ++package de.measite.smack; ++ ++import org.jivesoftware.smack.debugger.SmackDebugger; ++import org.jivesoftware.smack.ConnectionListener; ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.util.*; ++ ++import android.util.Log; ++ ++import java.io.Reader; ++import java.io.Writer; ++import java.text.SimpleDateFormat; ++import java.util.Date; ++ ++/** ++ * Very simple debugger that prints to the android log the sent and received stanzas. Use ++ * this debugger with caution since printing to the console is an expensive operation that may ++ * even block the thread since only one thread may print at a time.

++ *

++ * It is possible to not only print the raw sent and received stanzas but also the interpreted ++ * packets by Smack. By default interpreted packets won't be printed. To enable this feature ++ * just change the printInterpreted static variable to true. ++ * ++ * @author Gaston Dombiak ++ */ ++public class AndroidDebugger implements SmackDebugger { ++ ++ public static boolean printInterpreted = false; ++ private SimpleDateFormat dateFormatter = new SimpleDateFormat("hh:mm:ss aaa"); ++ ++ private Connection connection = null; ++ ++ private PacketListener listener = null; ++ private ConnectionListener connListener = null; ++ ++ private Writer writer; ++ private Reader reader; ++ private ReaderListener readerListener; ++ private WriterListener writerListener; ++ ++ public AndroidDebugger(Connection connection, Writer writer, Reader reader) { ++ this.connection = connection; ++ this.writer = writer; ++ this.reader = reader; ++ createDebug(); ++ } ++ ++ /** ++ * Creates the listeners that will print in the console when new activity is detected. ++ */ ++ private void createDebug() { ++ // Create a special Reader that wraps the main Reader and logs data to the GUI. ++ ObservableReader debugReader = new ObservableReader(reader); ++ readerListener = new ReaderListener() { ++ public void read(String str) { ++ Log.d("SMACK", ++ dateFormatter.format(new Date()) + " RCV (" + connection.hashCode() + ++ "): " + ++ str); ++ } ++ }; ++ debugReader.addReaderListener(readerListener); ++ ++ // Create a special Writer that wraps the main Writer and logs data to the GUI. ++ ObservableWriter debugWriter = new ObservableWriter(writer); ++ writerListener = new WriterListener() { ++ public void write(String str) { ++ Log.d("SMACK", ++ dateFormatter.format(new Date()) + " SENT (" + connection.hashCode() + ++ "): " + ++ str); ++ } ++ }; ++ debugWriter.addWriterListener(writerListener); ++ ++ // Assign the reader/writer objects to use the debug versions. The packet reader ++ // and writer will use the debug versions when they are created. ++ reader = debugReader; ++ writer = debugWriter; ++ ++ // Create a thread that will listen for all incoming packets and write them to ++ // the GUI. This is what we call "interpreted" packet data, since it's the packet ++ // data as Smack sees it and not as it's coming in as raw XML. ++ listener = new PacketListener() { ++ public void processPacket(Packet packet) { ++ if (printInterpreted) { ++ Log.d("SMACK", ++ dateFormatter.format(new Date()) + " RCV PKT (" + ++ connection.hashCode() + ++ "): " + ++ packet.toXML()); ++ } ++ } ++ }; ++ ++ connListener = new ConnectionListener() { ++ public void connectionClosed() { ++ Log.d("SMACK", ++ dateFormatter.format(new Date()) + " Connection closed (" + ++ connection.hashCode() + ++ ")"); ++ } ++ ++ public void connectionClosedOnError(Exception e) { ++ Log.d("SMACK", ++ dateFormatter.format(new Date()) + ++ " Connection closed due to an exception (" + ++ connection.hashCode() + ++ ")"); ++ e.printStackTrace(); ++ } ++ public void reconnectionFailed(Exception e) { ++ Log.d("SMACK", ++ dateFormatter.format(new Date()) + ++ " Reconnection failed due to an exception (" + ++ connection.hashCode() + ++ ")"); ++ e.printStackTrace(); ++ } ++ public void reconnectionSuccessful() { ++ Log.d("SMACK", ++ dateFormatter.format(new Date()) + " Connection reconnected (" + ++ connection.hashCode() + ++ ")"); ++ } ++ public void reconnectingIn(int seconds) { ++ Log.d("SMACK", ++ dateFormatter.format(new Date()) + " Connection (" + ++ connection.hashCode() + ++ ") will reconnect in " + seconds); ++ } ++ }; ++ } ++ ++ public Reader newConnectionReader(Reader newReader) { ++ ((ObservableReader)reader).removeReaderListener(readerListener); ++ ObservableReader debugReader = new ObservableReader(newReader); ++ debugReader.addReaderListener(readerListener); ++ reader = debugReader; ++ return reader; ++ } ++ ++ public Writer newConnectionWriter(Writer newWriter) { ++ ((ObservableWriter)writer).removeWriterListener(writerListener); ++ ObservableWriter debugWriter = new ObservableWriter(newWriter); ++ debugWriter.addWriterListener(writerListener); ++ writer = debugWriter; ++ return writer; ++ } ++ ++ public void userHasLogged(String user) { ++ boolean isAnonymous = "".equals(StringUtils.parseName(user)); ++ String title = ++ "User logged (" + connection.hashCode() + "): " ++ + (isAnonymous ? "" : StringUtils.parseBareAddress(user)) ++ + "@" ++ + connection.getServiceName() ++ + ":" ++ + connection.getPort(); ++ title += "/" + StringUtils.parseResource(user); ++ Log.d("SMACK", title); ++ // Add the connection listener to the connection so that the debugger can be notified ++ // whenever the connection is closed. ++ connection.addConnectionListener(connListener); ++ } ++ ++ public Reader getReader() { ++ return reader; ++ } ++ ++ public Writer getWriter() { ++ return writer; ++ } ++ ++ public PacketListener getReaderListener() { ++ return listener; ++ } ++ ++ public PacketListener getWriterListener() { ++ return null; ++ } ++} ++ +diff --git a/external/asmack/build/src/trunk/de/measite/smack/Sasl.java b/external/asmack/build/src/trunk/de/measite/smack/Sasl.java +new file mode 100644 +index 0000000..a59135d +--- /dev/null ++++ b/external/asmack/build/src/trunk/de/measite/smack/Sasl.java +@@ -0,0 +1,108 @@ ++/* ++ * Copyright 2009 Rene Treffer ++ * ++ * 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 de.measite.smack; ++ ++import java.util.Enumeration; ++import java.util.Hashtable; ++import java.util.Map; ++ ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++import org.apache.harmony.javax.security.sasl.SaslClient; ++import org.apache.harmony.javax.security.sasl.SaslException; ++import org.apache.harmony.javax.security.sasl.SaslServer; ++import org.apache.harmony.javax.security.sasl.SaslServerFactory; ++ ++public class Sasl { ++ ++ // SaslClientFactory service name ++ private static final String CLIENTFACTORYSRV = "SaslClientFactory"; //$NON-NLS-1$ ++ ++ // SaslServerFactory service name ++ private static final String SERVERFACTORYSRV = "SaslServerFactory"; //$NON-NLS-1$ ++ ++ public static final String POLICY_NOPLAINTEXT = "javax.security.sasl.policy.noplaintext"; //$NON-NLS-1$ ++ ++ public static final String POLICY_NOACTIVE = "javax.security.sasl.policy.noactive"; //$NON-NLS-1$ ++ ++ public static final String POLICY_NODICTIONARY = "javax.security.sasl.policy.nodictionary"; //$NON-NLS-1$ ++ ++ public static final String POLICY_NOANONYMOUS = "javax.security.sasl.policy.noanonymous"; //$NON-NLS-1$ ++ ++ public static final String POLICY_FORWARD_SECRECY = "javax.security.sasl.policy.forward"; //$NON-NLS-1$ ++ ++ public static final String POLICY_PASS_CREDENTIALS = "javax.security.sasl.policy.credentials"; //$NON-NLS-1$ ++ ++ public static final String MAX_BUFFER = "javax.security.sasl.maxbuffer"; //$NON-NLS-1$ ++ ++ public static final String RAW_SEND_SIZE = "javax.security.sasl.rawsendsize"; //$NON-NLS-1$ ++ ++ public static final String REUSE = "javax.security.sasl.reuse"; //$NON-NLS-1$ ++ ++ public static final String QOP = "javax.security.sasl.qop"; //$NON-NLS-1$ ++ ++ public static final String STRENGTH = "javax.security.sasl.strength"; //$NON-NLS-1$ ++ ++ public static final String SERVER_AUTH = "javax.security.sasl.server.authentication"; //$NON-NLS-1$ ++ ++ public static Enumeration getSaslClientFactories() { ++ Hashtable factories = new Hashtable(); ++ factories.put(new SaslClientFactory(), new Object()); ++ return factories.keys(); ++ } ++ ++ public static Enumeration getSaslServerFactories() { ++ return org.apache.harmony.javax.security.sasl.Sasl.getSaslServerFactories(); ++ } ++ ++ public static SaslServer createSaslServer(String mechanism, String protocol, ++ String serverName, Map prop, CallbackHandler cbh) throws SaslException { ++ return org.apache.harmony.javax.security.sasl.Sasl.createSaslServer(mechanism, protocol, serverName, prop, cbh); ++ } ++ ++ public static SaslClient createSaslClient(String[] mechanisms, String authanticationID, ++ String protocol, String serverName, Map prop, CallbackHandler cbh) ++ throws SaslException { ++ if (mechanisms == null) { ++ throw new NullPointerException("auth.33"); //$NON-NLS-1$ ++ } ++ SaslClientFactory fact = getSaslClientFactories().nextElement(); ++ String[] mech = fact.getMechanismNames(null); ++ boolean is = false; ++ if (mech != null) { ++ for (int j = 0; j < mech.length; j++) { ++ for (int n = 0; n < mechanisms.length; n++) { ++ if (mech[j].equals(mechanisms[n])) { ++ is = true; ++ break; ++ } ++ } ++ } ++ } ++ if (is) { ++ return fact.createSaslClient( ++ mechanisms, ++ authanticationID, ++ protocol, ++ serverName, ++ prop, ++ cbh ++ ); ++ } ++ return null; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/de/measite/smack/SaslClientFactory.java b/external/asmack/build/src/trunk/de/measite/smack/SaslClientFactory.java +new file mode 100644 +index 0000000..2fa1ebd +--- /dev/null ++++ b/external/asmack/build/src/trunk/de/measite/smack/SaslClientFactory.java +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2009 Rene Treffer ++ * ++ * 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 de.measite.smack; ++ ++import java.util.Map; ++ ++import com.novell.sasl.client.DigestMD5SaslClient; ++ ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++import org.apache.harmony.javax.security.sasl.SaslClient; ++import org.apache.harmony.javax.security.sasl.SaslException; ++import org.apache.qpid.management.common.sasl.PlainSaslClient; ++ ++public class SaslClientFactory implements ++ org.apache.harmony.javax.security.sasl.SaslClientFactory { ++ ++ @Override ++ public SaslClient createSaslClient(String[] mechanisms, ++ String authorizationId, String protocol, String serverName, ++ Map props, CallbackHandler cbh) throws SaslException { ++ for (String mech: mechanisms) { ++ if ("PLAIN".equals(mech)) { ++ return new PlainSaslClient(authorizationId, cbh); ++ } else ++ if ("DIGEST-MD5".equals(mech)) { ++ return DigestMD5SaslClient.getClient( ++ authorizationId, ++ protocol, ++ serverName, ++ props, ++ cbh ++ ); ++ } ++ } ++ return null; ++ } ++ ++ @Override ++ public String[] getMechanismNames(Map props) { ++ return new String[]{ ++ "PLAIN", ++ "DIGEST-MD5" ++ }; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/AuthPermission.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/AuthPermission.java +new file mode 100644 +index 0000000..bb12554 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/AuthPermission.java +@@ -0,0 +1,97 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth; ++ ++import java.security.BasicPermission; ++ ++ ++ ++/** ++ * Governs the use of methods in this package and also its subpackages. A ++ * target name of the permission specifies which methods are allowed ++ * without specifying the concrete action lists. Possible target names and ++ * associated authentication permissions are: ++ * ++ *

++ *    doAs                      invoke Subject.doAs methods.
++ *    doAsPrivileged            invoke the Subject.doAsPrivileged methods.
++ *    getSubject                invoke Subject.getSubject().
++ *    getSubjectFromDomainCombiner    invoke SubjectDomainCombiner.getSubject().
++ *    setReadOnly               invoke Subject.setReadonly().
++ *    modifyPrincipals          modify the set of principals
++ *                              associated with a Subject.
++ *    modifyPublicCredentials   modify the set of public credentials
++ *                              associated with a Subject.
++ *    modifyPrivateCredentials  modify the set of private credentials
++ *                              associated with a Subject.
++ *    refreshCredential         invoke the refresh method on a credential of a
++ *                              refreshable credential class.
++ *    destroyCredential         invoke the destroy method on a credential of a
++ *                              destroyable credential class.
++ *    createLoginContext.name   instantiate a LoginContext with the
++ *                              specified name. The wildcard name ('*')
++ *                              allows to a LoginContext of any name.
++ *    getLoginConfiguration     invoke the getConfiguration method of
++ *                              javax.security.auth.login.Configuration.
++ *    refreshLoginConfiguration Invoke the refresh method of
++ *                              javax.security.auth.login.Configuration.
++ * 
++ */ ++public final class AuthPermission extends BasicPermission { ++ ++ private static final long serialVersionUID = 5806031445061587174L; ++ ++ private static final String CREATE_LOGIN_CONTEXT = "createLoginContext"; //$NON-NLS-1$ ++ ++ private static final String CREATE_LOGIN_CONTEXT_ANY = "createLoginContext.*"; //$NON-NLS-1$ ++ ++ // inits permission name. ++ private static String init(String name) { ++ ++ if (name == null) { ++ throw new NullPointerException("auth.13"); //$NON-NLS-1$ ++ } ++ ++ if (CREATE_LOGIN_CONTEXT.equals(name)) { ++ return CREATE_LOGIN_CONTEXT_ANY; ++ } ++ return name; ++ } ++ ++ /** ++ * Creates an authentication permission with the specified target name. ++ * ++ * @param name ++ * the target name of this authentication permission. ++ */ ++ public AuthPermission(String name) { ++ super(init(name)); ++ } ++ ++ /** ++ * Creates an authentication permission with the specified target name. ++ * ++ * @param name ++ * the target name of this authentication permission. ++ * @param actions ++ * this parameter is ignored and should be {@code null}. ++ */ ++ public AuthPermission(String name, String actions) { ++ super(init(name), actions); ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/DestroyFailedException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/DestroyFailedException.java +new file mode 100644 +index 0000000..7c7ea79 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/DestroyFailedException.java +@@ -0,0 +1,44 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth; ++ ++/** ++ * Signals that the {@link Destroyable#destroy()} method failed. ++ */ ++public class DestroyFailedException extends Exception { ++ ++ private static final long serialVersionUID = -7790152857282749162L; ++ ++ /** ++ * Creates an exception of type {@code DestroyFailedException}. ++ */ ++ public DestroyFailedException() { ++ super(); ++ } ++ ++ /** ++ * Creates an exception of type {@code DestroyFailedException}. ++ * ++ * @param message ++ * A detail message that describes the reason for this exception. ++ */ ++ public DestroyFailedException(String message) { ++ super(message); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/Destroyable.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/Destroyable.java +new file mode 100644 +index 0000000..12a107b +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/Destroyable.java +@@ -0,0 +1,43 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth; ++ ++/** ++ * Allows for special treatment of sensitive information, when it comes to ++ * destroying or clearing of the data. ++ */ ++public interface Destroyable { ++ ++ /** ++ * Erases the sensitive information. Once an object is destroyed any calls ++ * to its methods will throw an {@code IllegalStateException}. If it does ++ * not succeed a DestroyFailedException is thrown. ++ * ++ * @throws DestroyFailedException ++ * if the information cannot be erased. ++ */ ++ void destroy() throws DestroyFailedException; ++ ++ /** ++ * Returns {@code true} once an object has been safely destroyed. ++ * ++ * @return whether the object has been safely destroyed. ++ */ ++ boolean isDestroyed(); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/PrivateCredentialPermission.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/PrivateCredentialPermission.java +new file mode 100644 +index 0000000..d62bb24 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/PrivateCredentialPermission.java +@@ -0,0 +1,395 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth; ++ ++import java.io.IOException; ++import java.io.ObjectInputStream; ++import java.io.Serializable; ++import java.security.Permission; ++import java.security.PermissionCollection; ++import java.security.Principal; ++import java.util.Set; ++ ++ ++ ++/** ++ * Protects private credential objects belonging to a {@code Subject}. It has ++ * only one action which is "read". The target name of this permission has a ++ * special syntax: ++ * ++ *
++ * targetName = CredentialClass {PrincipalClass "PrincipalName"}*
++ * 
++ * ++ * First it states a credential class and is followed then by a list of one or ++ * more principals identifying the subject. ++ *

++ * The principals on their part are specified as the name of the {@code ++ * Principal} class followed by the principal name in quotes. For example, the ++ * following file may define permission to read the private credentials of a ++ * principal named "Bob": "com.sun.PrivateCredential com.sun.Principal \"Bob\"" ++ *

++ * The syntax also allows the use of the wildcard "*" in place of {@code ++ * CredentialClass} or {@code PrincipalClass} and/or {@code PrincipalName}. ++ * ++ * @see Principal ++ */ ++public final class PrivateCredentialPermission extends Permission { ++ ++ private static final long serialVersionUID = 5284372143517237068L; ++ ++ // allowed action ++ private static final String READ = "read"; //$NON-NLS-1$ ++ ++ private String credentialClass; ++ ++ // current offset ++ private transient int offset; ++ ++ // owners set ++ private transient CredOwner[] set; ++ ++ /** ++ * Creates a new permission for private credentials specified by the target ++ * name {@code name} and an {@code action}. The action is always ++ * {@code "read"}. ++ * ++ * @param name ++ * the target name of the permission. ++ * @param action ++ * the action {@code "read"}. ++ */ ++ public PrivateCredentialPermission(String name, String action) { ++ super(name); ++ if (READ.equalsIgnoreCase(action)) { ++ initTargetName(name); ++ } else { ++ throw new IllegalArgumentException("auth.11"); //$NON-NLS-1$ ++ } ++ } ++ ++ /** ++ * Creates a {@code PrivateCredentialPermission} from the {@code Credential} ++ * class and set of principals. ++ * ++ * @param credentialClass ++ * the credential class name. ++ * @param principals ++ * the set of principals. ++ */ ++ PrivateCredentialPermission(String credentialClass, Set principals) { ++ super(credentialClass); ++ this.credentialClass = credentialClass; ++ ++ set = new CredOwner[principals.size()]; ++ for (Principal p : principals) { ++ CredOwner element = new CredOwner(p.getClass().getName(), p.getName()); ++ // check for duplicate elements ++ boolean found = false; ++ for (int ii = 0; ii < offset; ii++) { ++ if (set[ii].equals(element)) { ++ found = true; ++ break; ++ } ++ } ++ if (!found) { ++ set[offset++] = element; ++ } ++ } ++ } ++ ++ /** ++ * Initialize a PrivateCredentialPermission object and checks that a target ++ * name has a correct format: CredentialClass 1*(PrincipalClass ++ * "PrincipalName") ++ */ ++ private void initTargetName(String name) { ++ ++ if (name == null) { ++ throw new NullPointerException("auth.0E"); //$NON-NLS-1$ ++ } ++ ++ // check empty string ++ name = name.trim(); ++ if (name.length() == 0) { ++ throw new IllegalArgumentException("auth.0F"); //$NON-NLS-1$ ++ } ++ ++ // get CredentialClass ++ int beg = name.indexOf(' '); ++ if (beg == -1) { ++ throw new IllegalArgumentException("auth.10"); //$NON-NLS-1$ ++ } ++ credentialClass = name.substring(0, beg); ++ ++ // get a number of pairs: PrincipalClass "PrincipalName" ++ beg++; ++ int count = 0; ++ int nameLength = name.length(); ++ for (int i, j = 0; beg < nameLength; beg = j + 2, count++) { ++ i = name.indexOf(' ', beg); ++ j = name.indexOf('"', i + 2); ++ ++ if (i == -1 || j == -1 || name.charAt(i + 1) != '"') { ++ throw new IllegalArgumentException("auth.10"); //$NON-NLS-1$ ++ } ++ } ++ ++ // name MUST have one pair at least ++ if (count < 1) { ++ throw new IllegalArgumentException("auth.10"); //$NON-NLS-1$ ++ } ++ ++ beg = name.indexOf(' '); ++ beg++; ++ ++ // populate principal set with instances of CredOwner class ++ String principalClass; ++ String principalName; ++ ++ set = new CredOwner[count]; ++ for (int index = 0, i, j; index < count; beg = j + 2, index++) { ++ i = name.indexOf(' ', beg); ++ j = name.indexOf('"', i + 2); ++ ++ principalClass = name.substring(beg, i); ++ principalName = name.substring(i + 2, j); ++ ++ CredOwner element = new CredOwner(principalClass, principalName); ++ // check for duplicate elements ++ boolean found = false; ++ for (int ii = 0; ii < offset; ii++) { ++ if (set[ii].equals(element)) { ++ found = true; ++ break; ++ } ++ } ++ if (!found) { ++ set[offset++] = element; ++ } ++ } ++ } ++ ++ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ++ ois.defaultReadObject(); ++ initTargetName(getName()); ++ } ++ ++ /** ++ * Returns the principal's classes and names associated with this {@code ++ * PrivateCredentialPermission} as a two dimensional array. The first ++ * dimension of the array corresponds to the number of principals. The ++ * second dimension defines either the name of the {@code PrincipalClass} ++ * [x][0] or the value of {@code PrincipalName} [x][1]. ++ *

++ * This corresponds to the the target name's syntax: ++ * ++ *

++     * targetName = CredentialClass {PrincipalClass "PrincipalName"}*
++     * 
++ * ++ * @return the principal classes and names associated with this {@code ++ * PrivateCredentialPermission}. ++ */ ++ public String[][] getPrincipals() { ++ ++ String[][] s = new String[offset][2]; ++ ++ for (int i = 0; i < s.length; i++) { ++ s[i][0] = set[i].principalClass; ++ s[i][1] = set[i].principalName; ++ } ++ return s; ++ } ++ ++ @Override ++ public String getActions() { ++ return READ; ++ } ++ ++ /** ++ * Returns the class name of the credential associated with this permission. ++ * ++ * @return the class name of the credential associated with this permission. ++ */ ++ public String getCredentialClass() { ++ return credentialClass; ++ } ++ ++ @Override ++ public int hashCode() { ++ int hash = 0; ++ for (int i = 0; i < offset; i++) { ++ hash = hash + set[i].hashCode(); ++ } ++ return getCredentialClass().hashCode() + hash; ++ } ++ ++ @Override ++ public boolean equals(Object obj) { ++ if (obj == this) { ++ return true; ++ } ++ ++ if (obj == null || this.getClass() != obj.getClass()) { ++ return false; ++ } ++ ++ PrivateCredentialPermission that = (PrivateCredentialPermission) obj; ++ ++ return credentialClass.equals(that.credentialClass) && (offset == that.offset) ++ && sameMembers(set, that.set, offset); ++ } ++ ++ @Override ++ public boolean implies(Permission permission) { ++ ++ if (permission == null || this.getClass() != permission.getClass()) { ++ return false; ++ } ++ ++ PrivateCredentialPermission that = (PrivateCredentialPermission) permission; ++ ++ if (!("*".equals(credentialClass) || credentialClass //$NON-NLS-1$ ++ .equals(that.getCredentialClass()))) { ++ return false; ++ } ++ ++ if (that.offset == 0) { ++ return true; ++ } ++ ++ CredOwner[] thisCo = set; ++ CredOwner[] thatCo = that.set; ++ int thisPrincipalsSize = offset; ++ int thatPrincipalsSize = that.offset; ++ for (int i = 0, j; i < thisPrincipalsSize; i++) { ++ for (j = 0; j < thatPrincipalsSize; j++) { ++ if (thisCo[i].implies(thatCo[j])) { ++ break; ++ } ++ } ++ if (j == thatCo.length) { ++ return false; ++ } ++ } ++ return true; ++ } ++ ++ @Override ++ public PermissionCollection newPermissionCollection() { ++ return null; ++ } ++ ++ /** ++ * Returns true if the two arrays have the same length, and every member of ++ * one array is contained in another array ++ */ ++ private boolean sameMembers(Object[] ar1, Object[] ar2, int length) { ++ if (ar1 == null && ar2 == null) { ++ return true; ++ } ++ if (ar1 == null || ar2 == null) { ++ return false; ++ } ++ boolean found; ++ for (int i = 0; i < length; i++) { ++ found = false; ++ for (int j = 0; j < length; j++) { ++ if (ar1[i].equals(ar2[j])) { ++ found = true; ++ break; ++ } ++ } ++ if (!found) { ++ return false; ++ } ++ } ++ return true; ++ } ++ ++ private static final class CredOwner implements Serializable { ++ ++ private static final long serialVersionUID = -5607449830436408266L; ++ ++ String principalClass; ++ ++ String principalName; ++ ++ // whether class name contains wildcards ++ private transient boolean isClassWildcard; ++ ++ // whether pname contains wildcards ++ private transient boolean isPNameWildcard; ++ ++ // Creates a new CredOwner with the specified Principal Class and Principal Name ++ CredOwner(String principalClass, String principalName) { ++ super(); ++ if ("*".equals(principalClass)) { //$NON-NLS-1$ ++ isClassWildcard = true; ++ } ++ ++ if ("*".equals(principalName)) { //$NON-NLS-1$ ++ isPNameWildcard = true; ++ } ++ ++ if (isClassWildcard && !isPNameWildcard) { ++ throw new IllegalArgumentException("auth.12"); //$NON-NLS-1$ ++ } ++ ++ this.principalClass = principalClass; ++ this.principalName = principalName; ++ } ++ ++ // Checks if this CredOwner implies the specified Object. ++ boolean implies(Object obj) { ++ if (obj == this) { ++ return true; ++ } ++ ++ CredOwner co = (CredOwner) obj; ++ ++ if (isClassWildcard || principalClass.equals(co.principalClass)) { ++ if (isPNameWildcard || principalName.equals(co.principalName)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ // Checks two CredOwner objects for equality. ++ @Override ++ public boolean equals(Object obj) { ++ if (obj == this) { ++ return true; ++ } ++ if (obj instanceof CredOwner) { ++ CredOwner that = (CredOwner) obj; ++ return principalClass.equals(that.principalClass) ++ && principalName.equals(that.principalName); ++ } ++ return false; ++ } ++ ++ // Returns the hash code value for this object. ++ @Override ++ public int hashCode() { ++ return principalClass.hashCode() + principalName.hashCode(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/RefreshFailedException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/RefreshFailedException.java +new file mode 100644 +index 0000000..71bcc6b +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/RefreshFailedException.java +@@ -0,0 +1,31 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth; ++ ++public class RefreshFailedException extends Exception { ++ ++ private static final long serialVersionUID = 5058444488565265840L; ++ ++ public RefreshFailedException() { ++ super(); ++ } ++ ++ public RefreshFailedException(String message) { ++ super(message); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/Refreshable.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/Refreshable.java +new file mode 100644 +index 0000000..90b00cb +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/Refreshable.java +@@ -0,0 +1,26 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth; ++ ++public interface Refreshable { ++ ++ void refresh() throws RefreshFailedException; ++ ++ boolean isCurrent(); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/Subject.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/Subject.java +new file mode 100644 +index 0000000..142686e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/Subject.java +@@ -0,0 +1,782 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth; ++ ++import java.io.IOException; ++import java.io.ObjectInputStream; ++import java.io.ObjectOutputStream; ++import java.io.Serializable; ++import java.security.AccessControlContext; ++import java.security.AccessController; ++import java.security.DomainCombiner; ++import java.security.Permission; ++import java.security.Principal; ++import java.security.PrivilegedAction; ++import java.security.PrivilegedActionException; ++import java.security.PrivilegedExceptionAction; ++import java.security.ProtectionDomain; ++import java.util.AbstractSet; ++import java.util.Collection; ++import java.util.Iterator; ++import java.util.LinkedList; ++import java.util.Set; ++ ++ ++ ++/** ++ * The central class of the {@code javax.security.auth} package representing an ++ * authenticated user or entity (both referred to as "subject"). IT defines also ++ * the static methods that allow code to be run, and do modifications according ++ * to the subject's permissions. ++ *

++ * A subject has the following features: ++ *

    ++ *
  • A set of {@code Principal} objects specifying the identities bound to a ++ * {@code Subject} that distinguish it.
  • ++ *
  • Credentials (public and private) such as certificates, keys, or ++ * authentication proofs such as tickets
  • ++ *
++ */ ++public final class Subject implements Serializable { ++ ++ private static final long serialVersionUID = -8308522755600156056L; ++ ++ private static final AuthPermission _AS = new AuthPermission("doAs"); //$NON-NLS-1$ ++ ++ private static final AuthPermission _AS_PRIVILEGED = new AuthPermission( ++ "doAsPrivileged"); //$NON-NLS-1$ ++ ++ private static final AuthPermission _SUBJECT = new AuthPermission( ++ "getSubject"); //$NON-NLS-1$ ++ ++ private static final AuthPermission _PRINCIPALS = new AuthPermission( ++ "modifyPrincipals"); //$NON-NLS-1$ ++ ++ private static final AuthPermission _PRIVATE_CREDENTIALS = new AuthPermission( ++ "modifyPrivateCredentials"); //$NON-NLS-1$ ++ ++ private static final AuthPermission _PUBLIC_CREDENTIALS = new AuthPermission( ++ "modifyPublicCredentials"); //$NON-NLS-1$ ++ ++ private static final AuthPermission _READ_ONLY = new AuthPermission( ++ "setReadOnly"); //$NON-NLS-1$ ++ ++ private final Set principals; ++ ++ private boolean readOnly; ++ ++ // set of private credentials ++ private transient SecureSet privateCredentials; ++ ++ // set of public credentials ++ private transient SecureSet publicCredentials; ++ ++ /** ++ * The default constructor initializing the sets of public and private ++ * credentials and principals with the empty set. ++ */ ++ public Subject() { ++ super(); ++ principals = new SecureSet(_PRINCIPALS); ++ publicCredentials = new SecureSet(_PUBLIC_CREDENTIALS); ++ privateCredentials = new SecureSet(_PRIVATE_CREDENTIALS); ++ ++ readOnly = false; ++ } ++ ++ /** ++ * The constructor for the subject, setting its public and private ++ * credentials and principals according to the arguments. ++ * ++ * @param readOnly ++ * {@code true} if this {@code Subject} is read-only, thus ++ * preventing any modifications to be done. ++ * @param subjPrincipals ++ * the set of Principals that are attributed to this {@code ++ * Subject}. ++ * @param pubCredentials ++ * the set of public credentials that distinguish this {@code ++ * Subject}. ++ * @param privCredentials ++ * the set of private credentials that distinguish this {@code ++ * Subject}. ++ */ ++ public Subject(boolean readOnly, Set subjPrincipals, ++ Set pubCredentials, Set privCredentials) { ++ ++ if (subjPrincipals == null || pubCredentials == null || privCredentials == null) { ++ throw new NullPointerException(); ++ } ++ ++ principals = new SecureSet(_PRINCIPALS, subjPrincipals); ++ publicCredentials = new SecureSet(_PUBLIC_CREDENTIALS, pubCredentials); ++ privateCredentials = new SecureSet(_PRIVATE_CREDENTIALS, privCredentials); ++ ++ this.readOnly = readOnly; ++ } ++ ++ /** ++ * Runs the code defined by {@code action} using the permissions granted to ++ * the {@code Subject} itself and to the code as well. ++ * ++ * @param subject ++ * the distinguished {@code Subject}. ++ * @param action ++ * the code to be run. ++ * @return the {@code Object} returned when running the {@code action}. ++ */ ++ @SuppressWarnings("unchecked") ++ public static Object doAs(Subject subject, PrivilegedAction action) { ++ ++ checkPermission(_AS); ++ ++ return doAs_PrivilegedAction(subject, action, AccessController.getContext()); ++ } ++ ++ /** ++ * Run the code defined by {@code action} using the permissions granted to ++ * the {@code Subject} and to the code itself, additionally providing a more ++ * specific context. ++ * ++ * @param subject ++ * the distinguished {@code Subject}. ++ * @param action ++ * the code to be run. ++ * @param context ++ * the specific context in which the {@code action} is invoked. ++ * if {@code null} a new {@link AccessControlContext} is ++ * instantiated. ++ * @return the {@code Object} returned when running the {@code action}. ++ */ ++ @SuppressWarnings("unchecked") ++ public static Object doAsPrivileged(Subject subject, PrivilegedAction action, ++ AccessControlContext context) { ++ ++ checkPermission(_AS_PRIVILEGED); ++ ++ if (context == null) { ++ return doAs_PrivilegedAction(subject, action, new AccessControlContext( ++ new ProtectionDomain[0])); ++ } ++ return doAs_PrivilegedAction(subject, action, context); ++ } ++ ++ // instantiates a new context and passes it to AccessController ++ @SuppressWarnings("unchecked") ++ private static Object doAs_PrivilegedAction(Subject subject, PrivilegedAction action, ++ final AccessControlContext context) { ++ ++ AccessControlContext newContext; ++ ++ final SubjectDomainCombiner combiner; ++ if (subject == null) { ++ // performance optimization ++ // if subject is null there is nothing to combine ++ combiner = null; ++ } else { ++ combiner = new SubjectDomainCombiner(subject); ++ } ++ ++ PrivilegedAction dccAction = new PrivilegedAction() { ++ public Object run() { ++ ++ return new AccessControlContext(context, combiner); ++ } ++ }; ++ ++ newContext = (AccessControlContext) AccessController.doPrivileged(dccAction); ++ ++ return AccessController.doPrivileged(action, newContext); ++ } ++ ++ /** ++ * Runs the code defined by {@code action} using the permissions granted to ++ * the subject and to the code itself. ++ * ++ * @param subject ++ * the distinguished {@code Subject}. ++ * @param action ++ * the code to be run. ++ * @return the {@code Object} returned when running the {@code action}. ++ * @throws PrivilegedActionException ++ * if running the {@code action} throws an exception. ++ */ ++ @SuppressWarnings("unchecked") ++ public static Object doAs(Subject subject, PrivilegedExceptionAction action) ++ throws PrivilegedActionException { ++ ++ checkPermission(_AS); ++ ++ return doAs_PrivilegedExceptionAction(subject, action, AccessController.getContext()); ++ } ++ ++ /** ++ * Runs the code defined by {@code action} using the permissions granted to ++ * the subject and to the code itself, additionally providing a more ++ * specific context. ++ * ++ * @param subject ++ * the distinguished {@code Subject}. ++ * @param action ++ * the code to be run. ++ * @param context ++ * the specific context in which the {@code action} is invoked. ++ * if {@code null} a new {@link AccessControlContext} is ++ * instantiated. ++ * @return the {@code Object} returned when running the {@code action}. ++ * @throws PrivilegedActionException ++ * if running the {@code action} throws an exception. ++ */ ++ @SuppressWarnings("unchecked") ++ public static Object doAsPrivileged(Subject subject, ++ PrivilegedExceptionAction action, AccessControlContext context) ++ throws PrivilegedActionException { ++ ++ checkPermission(_AS_PRIVILEGED); ++ ++ if (context == null) { ++ return doAs_PrivilegedExceptionAction(subject, action, ++ new AccessControlContext(new ProtectionDomain[0])); ++ } ++ return doAs_PrivilegedExceptionAction(subject, action, context); ++ } ++ ++ // instantiates a new context and passes it to AccessController ++ @SuppressWarnings("unchecked") ++ private static Object doAs_PrivilegedExceptionAction(Subject subject, ++ PrivilegedExceptionAction action, final AccessControlContext context) ++ throws PrivilegedActionException { ++ ++ AccessControlContext newContext; ++ ++ final SubjectDomainCombiner combiner; ++ if (subject == null) { ++ // performance optimization ++ // if subject is null there is nothing to combine ++ combiner = null; ++ } else { ++ combiner = new SubjectDomainCombiner(subject); ++ } ++ ++ PrivilegedAction dccAction = new PrivilegedAction() { ++ public AccessControlContext run() { ++ return new AccessControlContext(context, combiner); ++ } ++ }; ++ ++ newContext = AccessController.doPrivileged(dccAction); ++ ++ return AccessController.doPrivileged(action, newContext); ++ } ++ ++ /** ++ * Checks two Subjects for equality. More specifically if the principals, ++ * public and private credentials are equal, equality for two {@code ++ * Subjects} is implied. ++ * ++ * @param obj ++ * the {@code Object} checked for equality with this {@code ++ * Subject}. ++ * @return {@code true} if the specified {@code Subject} is equal to this ++ * one. ++ */ ++ @Override ++ public boolean equals(Object obj) { ++ ++ if (this == obj) { ++ return true; ++ } ++ ++ if (obj == null || this.getClass() != obj.getClass()) { ++ return false; ++ } ++ ++ Subject that = (Subject) obj; ++ ++ if (principals.equals(that.principals) ++ && publicCredentials.equals(that.publicCredentials) ++ && privateCredentials.equals(that.privateCredentials)) { ++ return true; ++ } ++ return false; ++ } ++ ++ /** ++ * Returns this {@code Subject}'s {@link Principal}. ++ * ++ * @return this {@code Subject}'s {@link Principal}. ++ */ ++ public Set getPrincipals() { ++ return principals; ++ } ++ ++ ++ /** ++ * Returns this {@code Subject}'s {@link Principal} which is a subclass of ++ * the {@code Class} provided. ++ * ++ * @param c ++ * the {@code Class} as a criteria which the {@code Principal} ++ * returned must satisfy. ++ * @return this {@code Subject}'s {@link Principal}. Modifications to the ++ * returned set of {@code Principal}s do not affect this {@code ++ * Subject}'s set. ++ */ ++ public Set getPrincipals(Class c) { ++ return ((SecureSet) principals).get(c); ++ } ++ ++ /** ++ * Returns the private credentials associated with this {@code Subject}. ++ * ++ * @return the private credentials associated with this {@code Subject}. ++ */ ++ public Set getPrivateCredentials() { ++ return privateCredentials; ++ } ++ ++ /** ++ * Returns this {@code Subject}'s private credentials which are a subclass ++ * of the {@code Class} provided. ++ * ++ * @param c ++ * the {@code Class} as a criteria which the private credentials ++ * returned must satisfy. ++ * @return this {@code Subject}'s private credentials. Modifications to the ++ * returned set of credentials do not affect this {@code Subject}'s ++ * credentials. ++ */ ++ public Set getPrivateCredentials(Class c) { ++ return privateCredentials.get(c); ++ } ++ ++ /** ++ * Returns the public credentials associated with this {@code Subject}. ++ * ++ * @return the public credentials associated with this {@code Subject}. ++ */ ++ public Set getPublicCredentials() { ++ return publicCredentials; ++ } ++ ++ ++ /** ++ * Returns this {@code Subject}'s public credentials which are a subclass of ++ * the {@code Class} provided. ++ * ++ * @param c ++ * the {@code Class} as a criteria which the public credentials ++ * returned must satisfy. ++ * @return this {@code Subject}'s public credentials. Modifications to the ++ * returned set of credentials do not affect this {@code Subject}'s ++ * credentials. ++ */ ++ public Set getPublicCredentials(Class c) { ++ return publicCredentials.get(c); ++ } ++ ++ /** ++ * Returns a hash code of this {@code Subject}. ++ * ++ * @return a hash code of this {@code Subject}. ++ */ ++ @Override ++ public int hashCode() { ++ return principals.hashCode() + privateCredentials.hashCode() ++ + publicCredentials.hashCode(); ++ } ++ ++ /** ++ * Prevents from modifications being done to the credentials and {@link ++ * Principal} sets. After setting it to read-only this {@code Subject} can ++ * not be made writable again. The destroy method on the credentials still ++ * works though. ++ */ ++ public void setReadOnly() { ++ checkPermission(_READ_ONLY); ++ ++ readOnly = true; ++ } ++ ++ /** ++ * Returns whether this {@code Subject} is read-only or not. ++ * ++ * @return whether this {@code Subject} is read-only or not. ++ */ ++ public boolean isReadOnly() { ++ return readOnly; ++ } ++ ++ /** ++ * Returns a {@code String} representation of this {@code Subject}. ++ * ++ * @return a {@code String} representation of this {@code Subject}. ++ */ ++ @Override ++ public String toString() { ++ ++ StringBuilder buf = new StringBuilder("Subject:\n"); //$NON-NLS-1$ ++ ++ Iterator it = principals.iterator(); ++ while (it.hasNext()) { ++ buf.append("\tPrincipal: "); //$NON-NLS-1$ ++ buf.append(it.next()); ++ buf.append('\n'); ++ } ++ ++ it = publicCredentials.iterator(); ++ while (it.hasNext()) { ++ buf.append("\tPublic Credential: "); //$NON-NLS-1$ ++ buf.append(it.next()); ++ buf.append('\n'); ++ } ++ ++ int offset = buf.length() - 1; ++ it = privateCredentials.iterator(); ++ try { ++ while (it.hasNext()) { ++ buf.append("\tPrivate Credential: "); //$NON-NLS-1$ ++ buf.append(it.next()); ++ buf.append('\n'); ++ } ++ } catch (SecurityException e) { ++ buf.delete(offset, buf.length()); ++ buf.append("\tPrivate Credentials: no accessible information\n"); //$NON-NLS-1$ ++ } ++ return buf.toString(); ++ } ++ ++ private void readObject(ObjectInputStream in) throws IOException, ++ ClassNotFoundException { ++ ++ in.defaultReadObject(); ++ ++ publicCredentials = new SecureSet(_PUBLIC_CREDENTIALS); ++ privateCredentials = new SecureSet(_PRIVATE_CREDENTIALS); ++ } ++ ++ private void writeObject(ObjectOutputStream out) throws IOException { ++ out.defaultWriteObject(); ++ } ++ ++ /** ++ * Returns the {@code Subject} that was last associated with the {@code ++ * context} provided as argument. ++ * ++ * @param context ++ * the {@code context} that was associated with the ++ * {@code Subject}. ++ * @return the {@code Subject} that was last associated with the {@code ++ * context} provided as argument. ++ */ ++ public static Subject getSubject(final AccessControlContext context) { ++ checkPermission(_SUBJECT); ++ if (context == null) { ++ throw new NullPointerException("auth.09"); //$NON-NLS-1$ ++ } ++ PrivilegedAction action = new PrivilegedAction() { ++ public DomainCombiner run() { ++ return context.getDomainCombiner(); ++ } ++ }; ++ DomainCombiner combiner = AccessController.doPrivileged(action); ++ ++ if ((combiner == null) || !(combiner instanceof SubjectDomainCombiner)) { ++ return null; ++ } ++ return ((SubjectDomainCombiner) combiner).getSubject(); ++ } ++ ++ // checks passed permission ++ private static void checkPermission(Permission p) { ++ SecurityManager sm = System.getSecurityManager(); ++ if (sm != null) { ++ sm.checkPermission(p); ++ } ++ } ++ ++ // FIXME is used only in two places. remove? ++ private void checkState() { ++ if (readOnly) { ++ throw new IllegalStateException("auth.0A"); //$NON-NLS-1$ ++ } ++ } ++ ++ private final class SecureSet extends AbstractSet implements Serializable { ++ ++ /** ++ * Compatibility issue: see comments for setType variable ++ */ ++ private static final long serialVersionUID = 7911754171111800359L; ++ ++ private LinkedList elements; ++ ++ /* ++ * Is used to define a set type for serialization. ++ * ++ * A type can be principal, priv. or pub. credential set. The spec. ++ * doesn't clearly says that priv. and pub. credential sets can be ++ * serialized and what classes they are. It is only possible to figure ++ * out from writeObject method comments that priv. credential set is ++ * serializable and it is an instance of SecureSet class. So pub. ++ * credential was implemented by analogy ++ * ++ * Compatibility issue: the class follows its specified serial form. ++ * Also according to the serialization spec. adding new field is a ++ * compatible change. So is ok for principal set (because the default ++ * value for integer is zero). But priv. or pub. credential set it is ++ * not compatible because most probably other implementations resolve ++ * this issue in other way ++ */ ++ private int setType; ++ ++ // Defines principal set for serialization. ++ private static final int SET_Principal = 0; ++ ++ // Defines private credential set for serialization. ++ private static final int SET_PrivCred = 1; ++ ++ // Defines public credential set for serialization. ++ private static final int SET_PubCred = 2; ++ ++ // permission required to modify set ++ private transient AuthPermission permission; ++ ++ protected SecureSet(AuthPermission perm) { ++ permission = perm; ++ elements = new LinkedList(); ++ } ++ ++ // creates set from specified collection with specified permission ++ // all collection elements are verified before adding ++ protected SecureSet(AuthPermission perm, Collection s) { ++ this(perm); ++ ++ // Subject's constructor receives a Set, we can trusts if a set is from bootclasspath, ++ // and not to check whether it contains duplicates or not ++ boolean trust = s.getClass().getClassLoader() == null; ++ ++ Iterator it = s.iterator(); ++ while (it.hasNext()) { ++ SST o = it.next(); ++ verifyElement(o); ++ if (trust || !elements.contains(o)) { ++ elements.add(o); ++ } ++ } ++ } ++ ++ // verifies new set element ++ private void verifyElement(Object o) { ++ ++ if (o == null) { ++ throw new NullPointerException(); ++ } ++ if (permission == _PRINCIPALS && !(Principal.class.isAssignableFrom(o.getClass()))) { ++ throw new IllegalArgumentException("auth.0B"); //$NON-NLS-1$ ++ } ++ } ++ ++ /* ++ * verifies specified element, checks set state, and security permission ++ * to modify set before adding new element ++ */ ++ @Override ++ public boolean add(SST o) { ++ ++ verifyElement(o); ++ ++ checkState(); ++ checkPermission(permission); ++ ++ if (!elements.contains(o)) { ++ elements.add(o); ++ return true; ++ } ++ return false; ++ } ++ ++ // returns an instance of SecureIterator ++ @Override ++ public Iterator iterator() { ++ ++ if (permission == _PRIVATE_CREDENTIALS) { ++ /* ++ * private credential set requires iterator with additional ++ * security check (PrivateCredentialPermission) ++ */ ++ return new SecureIterator(elements.iterator()) { ++ /* ++ * checks permission to access next private credential moves ++ * to the next element even SecurityException was thrown ++ */ ++ @Override ++ public SST next() { ++ SST obj = iterator.next(); ++ checkPermission(new PrivateCredentialPermission(obj ++ .getClass().getName(), principals)); ++ return obj; ++ } ++ }; ++ } ++ return new SecureIterator(elements.iterator()); ++ } ++ ++ @Override ++ public boolean retainAll(Collection c) { ++ ++ if (c == null) { ++ throw new NullPointerException(); ++ } ++ return super.retainAll(c); ++ } ++ ++ @Override ++ public int size() { ++ return elements.size(); ++ } ++ ++ /** ++ * return set with elements that are instances or subclasses of the ++ * specified class ++ */ ++ protected final Set get(final Class c) { ++ ++ if (c == null) { ++ throw new NullPointerException(); ++ } ++ ++ AbstractSet s = new AbstractSet() { ++ private LinkedList elements = new LinkedList(); ++ ++ @Override ++ public boolean add(E o) { ++ ++ if (!c.isAssignableFrom(o.getClass())) { ++ throw new IllegalArgumentException( ++ "auth.0C " + c.getName()); //$NON-NLS-1$ ++ } ++ ++ if (elements.contains(o)) { ++ return false; ++ } ++ elements.add(o); ++ return true; ++ } ++ ++ @Override ++ public Iterator iterator() { ++ return elements.iterator(); ++ } ++ ++ @Override ++ public boolean retainAll(Collection c) { ++ ++ if (c == null) { ++ throw new NullPointerException(); ++ } ++ return super.retainAll(c); ++ } ++ ++ @Override ++ public int size() { ++ return elements.size(); ++ } ++ }; ++ ++ // FIXME must have permissions for requested priv. credentials ++ for (Iterator it = iterator(); it.hasNext();) { ++ SST o = it.next(); ++ if (c.isAssignableFrom(o.getClass())) { ++ s.add(c.cast(o)); ++ } ++ } ++ return s; ++ } ++ ++ private void readObject(ObjectInputStream in) throws IOException, ++ ClassNotFoundException { ++ in.defaultReadObject(); ++ ++ switch (setType) { ++ case SET_Principal: ++ permission = _PRINCIPALS; ++ break; ++ case SET_PrivCred: ++ permission = _PRIVATE_CREDENTIALS; ++ break; ++ case SET_PubCred: ++ permission = _PUBLIC_CREDENTIALS; ++ break; ++ default: ++ throw new IllegalArgumentException(); ++ } ++ ++ Iterator it = elements.iterator(); ++ while (it.hasNext()) { ++ verifyElement(it.next()); ++ } ++ } ++ ++ private void writeObject(ObjectOutputStream out) throws IOException { ++ ++ if (permission == _PRIVATE_CREDENTIALS) { ++ // does security check for each private credential ++ for (Iterator it = iterator(); it.hasNext();) { ++ it.next(); ++ } ++ setType = SET_PrivCred; ++ } else if (permission == _PRINCIPALS) { ++ setType = SET_Principal; ++ } else { ++ setType = SET_PubCred; ++ } ++ ++ out.defaultWriteObject(); ++ } ++ ++ /** ++ * Represents iterator for subject's secure set ++ */ ++ private class SecureIterator implements Iterator { ++ protected Iterator iterator; ++ ++ protected SecureIterator(Iterator iterator) { ++ this.iterator = iterator; ++ } ++ ++ public boolean hasNext() { ++ return iterator.hasNext(); ++ } ++ ++ public SST next() { ++ return iterator.next(); ++ } ++ ++ /** ++ * checks set state, and security permission to modify set before ++ * removing current element ++ */ ++ public void remove() { ++ checkState(); ++ checkPermission(permission); ++ iterator.remove(); ++ } ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/SubjectDomainCombiner.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/SubjectDomainCombiner.java +new file mode 100644 +index 0000000..edbb672 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/SubjectDomainCombiner.java +@@ -0,0 +1,123 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth; ++ ++import java.security.DomainCombiner; ++import java.security.Principal; ++import java.security.ProtectionDomain; ++import java.util.Set; ++ ++/** ++ * Merges permissions based on code source and code signers with permissions ++ * granted to the specified {@link Subject}. ++ */ ++public class SubjectDomainCombiner implements DomainCombiner { ++ ++ // subject to be associated ++ private Subject subject; ++ ++ // permission required to get a subject object ++ private static final AuthPermission _GET = new AuthPermission( ++ "getSubjectFromDomainCombiner"); //$NON-NLS-1$ ++ ++ /** ++ * Creates a domain combiner for the entity provided in {@code subject}. ++ * ++ * @param subject ++ * the entity to which this domain combiner is associated. ++ */ ++ public SubjectDomainCombiner(Subject subject) { ++ super(); ++ if (subject == null) { ++ throw new NullPointerException(); ++ } ++ this.subject = subject; ++ } ++ ++ /** ++ * Returns the entity to which this domain combiner is associated. ++ * ++ * @return the entity to which this domain combiner is associated. ++ */ ++ public Subject getSubject() { ++ SecurityManager sm = System.getSecurityManager(); ++ if (sm != null) { ++ sm.checkPermission(_GET); ++ } ++ ++ return subject; ++ } ++ ++ /** ++ * Merges the {@code ProtectionDomain} with the {@code Principal}s ++ * associated with the subject of this {@code SubjectDomainCombiner}. ++ * ++ * @param currentDomains ++ * the {@code ProtectionDomain}s associated with the context of ++ * the current thread. The domains must be sorted according to ++ * the execution order, the most recent residing at the ++ * beginning. ++ * @param assignedDomains ++ * the {@code ProtectionDomain}s from the parent thread based on ++ * code source and signers. ++ * @return a single {@code ProtectionDomain} array computed from the two ++ * provided arrays, or {@code null}. ++ * @see ProtectionDomain ++ */ ++ public ProtectionDomain[] combine(ProtectionDomain[] currentDomains, ++ ProtectionDomain[] assignedDomains) { ++ // get array length for combining protection domains ++ int len = 0; ++ if (currentDomains != null) { ++ len += currentDomains.length; ++ } ++ if (assignedDomains != null) { ++ len += assignedDomains.length; ++ } ++ if (len == 0) { ++ return null; ++ } ++ ++ ProtectionDomain[] pd = new ProtectionDomain[len]; ++ ++ // for each current domain substitute set of principal with subject's ++ int cur = 0; ++ if (currentDomains != null) { ++ ++ Set s = subject.getPrincipals(); ++ Principal[] p = s.toArray(new Principal[s.size()]); ++ ++ for (cur = 0; cur < currentDomains.length; cur++) { ++ if (currentDomains[cur] != null) { ++ ProtectionDomain newPD; ++ newPD = new ProtectionDomain(currentDomains[cur].getCodeSource(), ++ currentDomains[cur].getPermissions(), currentDomains[cur] ++ .getClassLoader(), p); ++ pd[cur] = newPD; ++ } ++ } ++ } ++ ++ // copy assigned domains ++ if (assignedDomains != null) { ++ System.arraycopy(assignedDomains, 0, pd, cur, assignedDomains.length); ++ } ++ ++ return pd; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/Callback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/Callback.java +new file mode 100644 +index 0000000..8fd745c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/Callback.java +@@ -0,0 +1,25 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback; ++ ++/** ++ * Defines an empty base interface for all {@code Callback}s used during ++ * authentication. ++ */ ++public interface Callback { ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/CallbackHandler.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/CallbackHandler.java +new file mode 100644 +index 0000000..d09fafa +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/CallbackHandler.java +@@ -0,0 +1,54 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback; ++ ++import java.io.IOException; ++ ++/** ++ * Needs to be implemented by classes that want to handle authentication ++ * {@link Callback}s. A single method {@link #handle(Callback[])} must be ++ * provided that checks the type of the incoming {@code Callback}s and reacts ++ * accordingly. {@code CallbackHandler}s can be installed per application. It is ++ * also possible to configure a system-default {@code CallbackHandler} by ++ * setting the {@code auth.login.defaultCallbackHandler} property in the ++ * standard {@code security.properties} file. ++ */ ++public interface CallbackHandler { ++ ++ /** ++ * Handles the actual {@link Callback}. A {@code CallbackHandler} needs to ++ * implement this method. In the method, it is free to select which {@code ++ * Callback}s it actually wants to handle and in which way. For example, a ++ * console-based {@code CallbackHandler} might choose to sequentially ask ++ * the user for login and password, if it implements these {@code Callback} ++ * s, whereas a GUI-based one might open a single dialog window for both ++ * values. If a {@code CallbackHandler} is not able to handle a specific ++ * {@code Callback}, it needs to throw an ++ * {@link UnsupportedCallbackException}. ++ * ++ * @param callbacks ++ * the array of {@code Callback}s that need handling ++ * @throws IOException ++ * if an I/O related error occurs ++ * @throws UnsupportedCallbackException ++ * if the {@code CallbackHandler} is not able to handle a ++ * specific {@code Callback} ++ */ ++ void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException; ++ ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/ChoiceCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/ChoiceCallback.java +new file mode 100644 +index 0000000..1e53fb6 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/ChoiceCallback.java +@@ -0,0 +1,109 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback; ++ ++import java.io.Serializable; ++ ++ ++ ++public class ChoiceCallback implements Callback, Serializable { ++ ++ private static final long serialVersionUID = -3975664071579892167L; ++ ++ private int defaultChoice; ++ ++ private String prompt; ++ ++ private boolean multipleSelectionsAllowed; ++ ++ private String[] choices; ++ ++ private int[] selections; ++ ++ private void setChoices(String[] choices) { ++ if (choices == null || choices.length == 0) { ++ throw new IllegalArgumentException("auth.1C"); //$NON-NLS-1$ ++ } ++ for (int i = 0; i < choices.length; i++) { ++ if (choices[i] == null || choices[i].length() == 0) { ++ throw new IllegalArgumentException("auth.1C"); //$NON-NLS-1$ ++ } ++ } ++ //FIXME: System.arraycopy(choices, 0 , new String[choices.length], 0, choices.length); ++ this.choices = choices; ++ ++ } ++ ++ private void setPrompt(String prompt) { ++ if (prompt == null || prompt.length() == 0) { ++ throw new IllegalArgumentException("auth.14"); //$NON-NLS-1$ ++ } ++ this.prompt = prompt; ++ } ++ ++ private void setDefaultChoice(int defaultChoice) { ++ if (0 > defaultChoice || defaultChoice >= choices.length) { ++ throw new IllegalArgumentException("auth.1D"); //$NON-NLS-1$ ++ } ++ this.defaultChoice = defaultChoice; ++ } ++ ++ public ChoiceCallback(String prompt, String[] choices, int defaultChoice, ++ boolean multipleSelectionsAllowed) { ++ super(); ++ setPrompt(prompt); ++ setChoices(choices); ++ setDefaultChoice(defaultChoice); ++ this.multipleSelectionsAllowed = multipleSelectionsAllowed; ++ } ++ ++ public boolean allowMultipleSelections() { ++ return multipleSelectionsAllowed; ++ } ++ ++ public String[] getChoices() { ++ return choices; ++ } ++ ++ public int getDefaultChoice() { ++ return defaultChoice; ++ } ++ ++ public String getPrompt() { ++ return prompt; ++ } ++ ++ public int[] getSelectedIndexes() { ++ return selections; ++ } ++ ++ public void setSelectedIndex(int selection) { ++ this.selections = new int[1]; ++ this.selections[0] = selection; ++ } ++ ++ public void setSelectedIndexes(int[] selections) { ++ if (!multipleSelectionsAllowed) { ++ throw new UnsupportedOperationException(); ++ } ++ this.selections = selections; ++ //FIXME: ++ // this.selections = new int[selections.length] ++ //System.arraycopy(selections, 0, this.selections, 0, this.selections.length); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/ConfirmationCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/ConfirmationCallback.java +new file mode 100644 +index 0000000..a1893f3 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/ConfirmationCallback.java +@@ -0,0 +1,234 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback; ++ ++import java.io.Serializable; ++ ++ ++ ++public class ConfirmationCallback implements Callback, Serializable { ++ ++ private static final long serialVersionUID = -9095656433782481624L; ++ ++ public static final int YES = 0; // default options ++ ++ public static final int NO = 1; ++ ++ public static final int CANCEL = 2; ++ ++ public static final int OK = 3; ++ ++ public static final int YES_NO_OPTION = 0; // options type ++ ++ public static final int YES_NO_CANCEL_OPTION = 1; ++ ++ public static final int OK_CANCEL_OPTION = 2; ++ ++ public static final int UNSPECIFIED_OPTION = -1; ++ ++ public static final int INFORMATION = 0; // messages type ++ ++ public static final int WARNING = 1; ++ ++ public static final int ERROR = 2; ++ ++ private String prompt; ++ ++ private int messageType; ++ ++ private int optionType = UNSPECIFIED_OPTION; ++ ++ private int defaultOption; ++ ++ private String[] options; ++ ++ private int selection; ++ ++ public ConfirmationCallback(int messageType, int optionType, int defaultOption) { ++ super(); ++ if (messageType > ERROR || messageType < INFORMATION) { ++ throw new IllegalArgumentException("auth.16"); //$NON-NLS-1$ ++ } ++ ++ switch (optionType) { ++ case YES_NO_OPTION: ++ if (defaultOption != YES && defaultOption != NO) { ++ throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$ ++ } ++ break; ++ case YES_NO_CANCEL_OPTION: ++ if (defaultOption != YES && defaultOption != NO && defaultOption != CANCEL) { ++ throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$ ++ } ++ break; ++ case OK_CANCEL_OPTION: ++ if (defaultOption != OK && defaultOption != CANCEL) { ++ throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$ ++ } ++ break; ++ default: ++ throw new IllegalArgumentException("auth.18"); //$NON-NLS-1$ ++ } ++ this.messageType = messageType; ++ this.optionType = optionType; ++ this.defaultOption = defaultOption; ++ } ++ ++ public ConfirmationCallback(int messageType, String[] options, int defaultOption) { ++ super(); ++ if (messageType > ERROR || messageType < INFORMATION) { ++ throw new IllegalArgumentException("auth.16"); //$NON-NLS-1$ ++ } ++ ++ if (options == null || options.length == 0) { ++ throw new IllegalArgumentException("auth.1A"); //$NON-NLS-1$ ++ } ++ for (int i = 0; i < options.length; i++) { ++ if (options[i] == null || options[i].length() == 0) { ++ throw new IllegalArgumentException("auth.1A"); //$NON-NLS-1$ ++ } ++ } ++ if (0 > defaultOption || defaultOption >= options.length) { ++ throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$ ++ } ++ // FIXME:System.arraycopy(options, 0 , new String[this.options.length], ++ // 0, this.options.length); ++ this.options = options; ++ this.defaultOption = defaultOption; ++ this.messageType = messageType; ++ } ++ ++ public ConfirmationCallback(String prompt, int messageType, int optionType, ++ int defaultOption) { ++ super(); ++ if (prompt == null || prompt.length() == 0) { ++ throw new IllegalArgumentException("auth.14"); //$NON-NLS-1$ ++ } ++ ++ if (messageType > ERROR || messageType < INFORMATION) { ++ throw new IllegalArgumentException("auth.16"); //$NON-NLS-1$ ++ } ++ ++ switch (optionType) { ++ case YES_NO_OPTION: ++ if (defaultOption != YES && defaultOption != NO) { ++ throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$ ++ } ++ break; ++ case YES_NO_CANCEL_OPTION: ++ if (defaultOption != YES && defaultOption != NO && defaultOption != CANCEL) { ++ throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$ ++ } ++ break; ++ case OK_CANCEL_OPTION: ++ if (defaultOption != OK && defaultOption != CANCEL) { ++ throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$ ++ } ++ break; ++ default: ++ throw new IllegalArgumentException("auth.18"); //$NON-NLS-1$ ++ } ++ this.prompt = prompt; ++ this.messageType = messageType; ++ this.optionType = optionType; ++ this.defaultOption = defaultOption; ++ } ++ ++ public ConfirmationCallback(String prompt, int messageType, String[] options, ++ int defaultOption) { ++ super(); ++ if (prompt == null || prompt.length() == 0) { ++ throw new IllegalArgumentException("auth.14"); //$NON-NLS-1$ ++ } ++ ++ if (messageType > ERROR || messageType < INFORMATION) { ++ throw new IllegalArgumentException("auth.16"); //$NON-NLS-1$ ++ } ++ ++ if (options == null || options.length == 0) { ++ throw new IllegalArgumentException("auth.1A"); //$NON-NLS-1$ ++ } ++ for (int i = 0; i < options.length; i++) { ++ if (options[i] == null || options[i].length() == 0) { ++ throw new IllegalArgumentException("auth.1A"); //$NON-NLS-1$ ++ } ++ } ++ if (0 > defaultOption || defaultOption >= options.length) { ++ throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$ ++ } ++ // FIXME:System.arraycopy(options, 0 , new String[this.options.length], ++ // 0, this.options.length); ++ this.options = options; ++ this.defaultOption = defaultOption; ++ this.messageType = messageType; ++ this.prompt = prompt; ++ } ++ ++ public String getPrompt() { ++ return prompt; ++ } ++ ++ public int getMessageType() { ++ return messageType; ++ } ++ ++ public int getDefaultOption() { ++ return defaultOption; ++ } ++ ++ public String[] getOptions() { ++ return options; ++ } ++ ++ public int getOptionType() { ++ return optionType; ++ } ++ ++ public int getSelectedIndex() { ++ return selection; ++ } ++ ++ public void setSelectedIndex(int selection) { ++ if (options != null) { ++ if (0 <= selection && selection <= options.length) { ++ this.selection = selection; ++ } else { ++ throw new ArrayIndexOutOfBoundsException("auth.1B"); //$NON-NLS-1$ ++ } ++ } else { ++ switch (optionType) { ++ case YES_NO_OPTION: ++ if (selection != YES && selection != NO) { ++ throw new IllegalArgumentException("auth.19"); //$NON-NLS-1$ ++ } ++ break; ++ case YES_NO_CANCEL_OPTION: ++ if (selection != YES && selection != NO && selection != CANCEL) { ++ throw new IllegalArgumentException("auth.19"); //$NON-NLS-1$ ++ } ++ break; ++ case OK_CANCEL_OPTION: ++ if (selection != OK && selection != CANCEL) { ++ throw new IllegalArgumentException("auth.19"); //$NON-NLS-1$ ++ } ++ break; ++ } ++ this.selection = selection; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/LanguageCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/LanguageCallback.java +new file mode 100644 +index 0000000..729bb49 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/LanguageCallback.java +@@ -0,0 +1,41 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback; ++ ++import java.io.Serializable; ++import java.util.Locale; ++ ++public class LanguageCallback implements Callback, Serializable { ++ ++ private static final long serialVersionUID = 2019050433478903213L; ++ ++ private Locale locale; ++ ++ public LanguageCallback() { ++ super(); ++ } ++ ++ public Locale getLocale() { ++ return locale; ++ } ++ ++ public void setLocale(Locale locale) { ++ this.locale = locale; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/NameCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/NameCallback.java +new file mode 100644 +index 0000000..97264ef +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/NameCallback.java +@@ -0,0 +1,74 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback; ++ ++import java.io.Serializable; ++ ++ ++ ++public class NameCallback implements Callback, Serializable { ++ ++ private static final long serialVersionUID = 3770938795909392253L; ++ ++ private String prompt; ++ ++ private String defaultName; ++ ++ private String inputName; ++ ++ private void setPrompt(String prompt) { ++ if (prompt == null || prompt.length() == 0) { ++ throw new IllegalArgumentException("auth.14"); //$NON-NLS-1$ ++ } ++ this.prompt = prompt; ++ } ++ ++ private void setDefaultName(String defaultName) { ++ if (defaultName == null || defaultName.length() == 0) { ++ throw new IllegalArgumentException("auth.1E"); //$NON-NLS-1$ ++ } ++ this.defaultName = defaultName; ++ } ++ ++ public NameCallback(String prompt) { ++ super(); ++ setPrompt(prompt); ++ } ++ ++ public NameCallback(String prompt, String defaultName) { ++ super(); ++ setPrompt(prompt); ++ setDefaultName(defaultName); ++ } ++ ++ public String getPrompt() { ++ return prompt; ++ } ++ ++ public String getDefaultName() { ++ return defaultName; ++ } ++ ++ public void setName(String name) { ++ this.inputName = name; ++ } ++ ++ public String getName() { ++ return inputName; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/PasswordCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/PasswordCallback.java +new file mode 100644 +index 0000000..bd142fc +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/PasswordCallback.java +@@ -0,0 +1,124 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback; ++ ++import java.io.Serializable; ++import java.util.Arrays; ++ ++ ++ ++/** ++ * Is used in conjunction with a {@link CallbackHandler} to retrieve a password ++ * when needed. ++ */ ++public class PasswordCallback implements Callback, Serializable { ++ ++ private static final long serialVersionUID = 2267422647454909926L; ++ ++ private String prompt; ++ ++ boolean echoOn; ++ ++ private char[] inputPassword; ++ ++ private void setPrompt(String prompt) throws IllegalArgumentException { ++ if (prompt == null || prompt.length() == 0) { ++ throw new IllegalArgumentException("auth.14"); //$NON-NLS-1$ ++ } ++ this.prompt = prompt; ++ } ++ ++ /** ++ * Creates a new {@code PasswordCallback} instance. ++ * ++ * @param prompt ++ * the message that should be displayed to the user ++ * @param echoOn ++ * determines whether the user input should be echoed ++ */ ++ public PasswordCallback(String prompt, boolean echoOn) { ++ super(); ++ setPrompt(prompt); ++ this.echoOn = echoOn; ++ } ++ ++ /** ++ * Returns the prompt that was specified when creating this {@code ++ * PasswordCallback} ++ * ++ * @return the prompt ++ */ ++ public String getPrompt() { ++ return prompt; ++ } ++ ++ /** ++ * Queries whether this {@code PasswordCallback} expects user input to be ++ * echoed, which is specified during the creation of the object. ++ * ++ * @return {@code true} if (and only if) user input should be echoed ++ */ ++ public boolean isEchoOn() { ++ return echoOn; ++ } ++ ++ /** ++ * Sets the password. The {@link CallbackHandler} that performs the actual ++ * provisioning or input of the password needs to call this method to hand ++ * back the password to the security service that requested it. ++ * ++ * @param password ++ * the password. A copy of this is stored, so subsequent changes ++ * to the input array do not affect the {@code PasswordCallback}. ++ */ ++ public void setPassword(char[] password) { ++ if (password == null) { ++ this.inputPassword = password; ++ } else { ++ inputPassword = new char[password.length]; ++ System.arraycopy(password, 0, inputPassword, 0, inputPassword.length); ++ } ++ } ++ ++ /** ++ * Returns the password. The security service that needs the password ++ * usually calls this method once the {@link CallbackHandler} has finished ++ * its work. ++ * ++ * @return the password. A copy of the internal password is created and ++ * returned, so subsequent changes to the internal password do not ++ * affect the result. ++ */ ++ public char[] getPassword() { ++ if (inputPassword != null) { ++ char[] tmp = new char[inputPassword.length]; ++ System.arraycopy(inputPassword, 0, tmp, 0, tmp.length); ++ return tmp; ++ } ++ return null; ++ } ++ ++ /** ++ * Clears the password stored in this {@code PasswordCallback}. ++ */ ++ public void clearPassword() { ++ if (inputPassword != null) { ++ Arrays.fill(inputPassword, '\u0000'); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/TextInputCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/TextInputCallback.java +new file mode 100644 +index 0000000..c7de222 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/TextInputCallback.java +@@ -0,0 +1,74 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback; ++ ++import java.io.Serializable; ++ ++ ++ ++public class TextInputCallback implements Callback, Serializable { ++ ++ private static final long serialVersionUID = -8064222478852811804L; ++ ++ private String defaultText; ++ ++ private String prompt; ++ ++ private String inputText; ++ ++ private void setPrompt(String prompt) { ++ if (prompt == null || prompt.length() == 0) { ++ throw new IllegalArgumentException("auth.14"); //$NON-NLS-1$ ++ } ++ this.prompt = prompt; ++ } ++ ++ private void setDefaultText(String defaultText) { ++ if (defaultText == null || defaultText.length() == 0) { ++ throw new IllegalArgumentException("auth.15"); //$NON-NLS-1$ ++ } ++ this.defaultText = defaultText; ++ } ++ ++ public TextInputCallback(String prompt) { ++ super(); ++ setPrompt(prompt); ++ } ++ ++ public TextInputCallback(String prompt, String defaultText) { ++ super(); ++ setPrompt(prompt); ++ setDefaultText(defaultText); ++ } ++ ++ public String getDefaultText() { ++ return defaultText; ++ } ++ ++ public String getPrompt() { ++ return prompt; ++ } ++ ++ public String getText() { ++ return inputText; ++ } ++ ++ public void setText(String text) { ++ this.inputText = text; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/TextOutputCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/TextOutputCallback.java +new file mode 100644 +index 0000000..23a72fa +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/TextOutputCallback.java +@@ -0,0 +1,56 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback; ++ ++import java.io.Serializable; ++ ++ ++ ++public class TextOutputCallback implements Callback, Serializable { ++ ++ private static final long serialVersionUID = 1689502495511663102L; ++ ++ public static final int INFORMATION = 0; ++ ++ public static final int WARNING = 1; ++ ++ public static final int ERROR = 2; ++ ++ private String message; ++ ++ private int messageType; ++ ++ public TextOutputCallback(int messageType, String message) { ++ if (messageType > ERROR || messageType < INFORMATION) { ++ throw new IllegalArgumentException("auth.16"); //$NON-NLS-1$ ++ } ++ if (message == null || message.length() == 0) { ++ throw new IllegalArgumentException("auth.1F"); //$NON-NLS-1$ ++ } ++ this.messageType = messageType; ++ this.message = message; ++ } ++ ++ public String getMessage() { ++ return message; ++ } ++ ++ public int getMessageType() { ++ return messageType; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/UnsupportedCallbackException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/UnsupportedCallbackException.java +new file mode 100644 +index 0000000..19f6e40 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/UnsupportedCallbackException.java +@@ -0,0 +1,64 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback; ++ ++/** ++ * Thrown when a {@link CallbackHandler} does not support a particular {@link ++ * Callback}. ++ */ ++public class UnsupportedCallbackException extends Exception { ++ ++ private static final long serialVersionUID = -6873556327655666839L; ++ ++ private Callback callback; ++ ++ /** ++ * Creates a new exception instance and initializes it with just the ++ * unsupported {@code Callback}, but no error message. ++ * ++ * @param callback ++ * the {@code Callback} ++ */ ++ public UnsupportedCallbackException(Callback callback) { ++ super(); ++ this.callback = callback; ++ } ++ ++ /** ++ * Creates a new exception instance and initializes it with both the ++ * unsupported {@code Callback} and an error message. ++ * ++ * @param callback ++ * the {@code Callback} ++ * @param message ++ * the error message ++ */ ++ public UnsupportedCallbackException(Callback callback, String message) { ++ super(message); ++ this.callback = callback; ++ } ++ ++ /** ++ * Returns the unsupported {@code Callback} that triggered this exception. ++ * ++ * @return the {@code Callback} ++ */ ++ public Callback getCallback() { ++ return callback; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountException.java +new file mode 100644 +index 0000000..c86e801 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountException.java +@@ -0,0 +1,31 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login; ++ ++public class AccountException extends LoginException { ++ ++ private static final long serialVersionUID = -2112878680072211787L; ++ ++ public AccountException() { ++ super(); ++ } ++ ++ public AccountException(String message) { ++ super(message); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountExpiredException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountExpiredException.java +new file mode 100644 +index 0000000..1e0ce4d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountExpiredException.java +@@ -0,0 +1,31 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login; ++ ++public class AccountExpiredException extends AccountException { ++ ++ private static final long serialVersionUID = -6064064890162661560L; ++ ++ public AccountExpiredException() { ++ super(); ++ } ++ ++ public AccountExpiredException(String message) { ++ super(message); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountLockedException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountLockedException.java +new file mode 100644 +index 0000000..47913a5 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountLockedException.java +@@ -0,0 +1,32 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login; ++ ++public class AccountLockedException extends AccountException { ++ ++ private static final long serialVersionUID = 8280345554014066334L; ++ ++ public AccountLockedException() { ++ super(); ++ } ++ ++ public AccountLockedException(String message) { ++ super(message); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountNotFoundException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountNotFoundException.java +new file mode 100644 +index 0000000..8ca9b96 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountNotFoundException.java +@@ -0,0 +1,32 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login; ++ ++public class AccountNotFoundException extends AccountException { ++ ++ private static final long serialVersionUID = 1498349563916294614L; ++ ++ public AccountNotFoundException() { ++ super(); ++ } ++ ++ public AccountNotFoundException(String message) { ++ super(message); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AppConfigurationEntry.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AppConfigurationEntry.java +new file mode 100644 +index 0000000..2a735dc +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AppConfigurationEntry.java +@@ -0,0 +1,95 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login; ++ ++import java.util.Collections; ++import java.util.Map; ++ ++ ++ ++public class AppConfigurationEntry { ++ ++ // the login module options ++ private final Map options; ++ ++ // the control flag ++ private final AppConfigurationEntry.LoginModuleControlFlag controlFlag; ++ ++ // the login module name ++ private final String loginModuleName; ++ ++ public AppConfigurationEntry(String loginModuleName, ++ AppConfigurationEntry.LoginModuleControlFlag controlFlag, Map options) { ++ ++ if (loginModuleName == null || loginModuleName.length() == 0) { ++ throw new IllegalArgumentException("auth.26"); //$NON-NLS-1$ ++ } ++ ++ if (controlFlag == null) { ++ throw new IllegalArgumentException("auth.27"); //$NON-NLS-1$ ++ } ++ ++ if (options == null) { ++ throw new IllegalArgumentException("auth.1A"); //$NON-NLS-1$ ++ } ++ ++ this.loginModuleName = loginModuleName; ++ this.controlFlag = controlFlag; ++ this.options = Collections.unmodifiableMap(options); ++ } ++ ++ public String getLoginModuleName() { ++ return loginModuleName; ++ } ++ ++ public LoginModuleControlFlag getControlFlag() { ++ return controlFlag; ++ } ++ ++ public Map getOptions() { ++ return options; ++ } ++ ++ public static class LoginModuleControlFlag { ++ ++ // the control flag ++ private final String flag; ++ ++ public static final LoginModuleControlFlag REQUIRED = new LoginModuleControlFlag( ++ "LoginModuleControlFlag: required"); //$NON-NLS-1$ ++ ++ public static final LoginModuleControlFlag REQUISITE = new LoginModuleControlFlag( ++ "LoginModuleControlFlag: requisite"); //$NON-NLS-1$ ++ ++ public static final LoginModuleControlFlag OPTIONAL = new LoginModuleControlFlag( ++ "LoginModuleControlFlag: optional"); //$NON-NLS-1$ ++ ++ public static final LoginModuleControlFlag SUFFICIENT = new LoginModuleControlFlag( ++ "LoginModuleControlFlag: sufficient"); //$NON-NLS-1$ ++ ++ // Creates the LoginModuleControlFlag object with specified a flag ++ private LoginModuleControlFlag(String flag) { ++ this.flag = flag; ++ } ++ ++ @Override ++ public String toString() { ++ return flag; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/Configuration.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/Configuration.java +new file mode 100644 +index 0000000..74c371f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/Configuration.java +@@ -0,0 +1,102 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login; ++ ++import java.security.AccessController; ++import org.apache.harmony.javax.security.auth.AuthPermission; ++ ++public abstract class Configuration { ++ ++ // the current configuration ++ private static Configuration configuration; ++ ++ // creates a AuthPermission object with a specify property ++ private static final AuthPermission GET_LOGIN_CONFIGURATION = new AuthPermission( ++ "getLoginConfiguration"); //$NON-NLS-1$ ++ ++ // creates a AuthPermission object with a specify property ++ private static final AuthPermission SET_LOGIN_CONFIGURATION = new AuthPermission( ++ "setLoginConfiguration"); //$NON-NLS-1$ ++ ++ // Key to security properties, defining default configuration provider. ++ private static final String LOGIN_CONFIGURATION_PROVIDER = "login.configuration.provider"; //$NON-NLS-1$ ++ ++ protected Configuration() { ++ super(); ++ } ++ ++ public static Configuration getConfiguration() { ++ SecurityManager sm = System.getSecurityManager(); ++ if (sm != null) { ++ sm.checkPermission(GET_LOGIN_CONFIGURATION); ++ } ++ return getAccessibleConfiguration(); ++ } ++ ++ /** ++ * Reads name of default configuration provider from security.properties, ++ * loads the class and instantiates the provider.
In case of any ++ * exception, wraps it with SecurityException and throws further. ++ */ ++ private static final Configuration getDefaultProvider() { ++ return new Configuration() { ++ ++ @Override ++ public void refresh() { ++ } ++ ++ @Override ++ public AppConfigurationEntry[] getAppConfigurationEntry( ++ String applicationName) { ++ return new AppConfigurationEntry[0]; ++ } ++ }; ++ } ++ ++ /** ++ * Shortcut accessor for friendly classes, to skip security checks. ++ * If active configuration was set to null, tries to load a default ++ * provider, so this method never returns null.
++ * This method is synchronized with setConfiguration() ++ */ ++ static Configuration getAccessibleConfiguration() { ++ Configuration current = configuration; ++ if (current == null) { ++ synchronized (Configuration.class) { ++ if (configuration == null) { ++ configuration = getDefaultProvider(); ++ } ++ return configuration; ++ } ++ } ++ return current; ++ } ++ ++ public static void setConfiguration(Configuration configuration) { ++ SecurityManager sm = System.getSecurityManager(); ++ if (sm != null) { ++ sm.checkPermission(SET_LOGIN_CONFIGURATION); ++ } ++ Configuration.configuration = configuration; ++ } ++ ++ public abstract AppConfigurationEntry[] getAppConfigurationEntry(String applicationName); ++ ++ public abstract void refresh(); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/CredentialException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/CredentialException.java +new file mode 100644 +index 0000000..e74e866 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/CredentialException.java +@@ -0,0 +1,32 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login; ++ ++public class CredentialException extends LoginException { ++ ++ private static final long serialVersionUID = -4772893876810601859L; ++ ++ public CredentialException() { ++ super(); ++ } ++ ++ public CredentialException(String message) { ++ super(message); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/CredentialExpiredException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/CredentialExpiredException.java +new file mode 100644 +index 0000000..3ca3ad7 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/CredentialExpiredException.java +@@ -0,0 +1,32 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login; ++ ++public class CredentialExpiredException extends CredentialException { ++ ++ private static final long serialVersionUID = -5344739593859737937L; ++ ++ public CredentialExpiredException() { ++ super(); ++ } ++ ++ public CredentialExpiredException(String message) { ++ super(message); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/CredentialNotFoundException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/CredentialNotFoundException.java +new file mode 100644 +index 0000000..ffd529f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/CredentialNotFoundException.java +@@ -0,0 +1,32 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login; ++ ++public class CredentialNotFoundException extends CredentialException { ++ ++ private static final long serialVersionUID = -7779934467214319475L; ++ ++ public CredentialNotFoundException() { ++ super(); ++ } ++ ++ public CredentialNotFoundException(String message) { ++ super(message); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/FailedLoginException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/FailedLoginException.java +new file mode 100644 +index 0000000..f689d99 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/FailedLoginException.java +@@ -0,0 +1,32 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login; ++ ++public class FailedLoginException extends LoginException { ++ ++ private static final long serialVersionUID = 802556922354616286L; ++ ++ public FailedLoginException() { ++ super(); ++ } ++ ++ public FailedLoginException(String message) { ++ super(message); ++ } ++ ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/LoginContext.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/LoginContext.java +new file mode 100644 +index 0000000..7d46278 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/LoginContext.java +@@ -0,0 +1,548 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login; ++ ++import java.io.IOException; ++import java.security.AccessController; ++import java.security.AccessControlContext; ++import java.security.PrivilegedExceptionAction; ++import java.security.PrivilegedActionException; ++ ++import java.security.Security; ++import java.util.HashMap; ++import java.util.Map; ++ ++import org.apache.harmony.javax.security.auth.Subject; ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++import org.apache.harmony.javax.security.auth.callback.Callback; ++import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException; ++import org.apache.harmony.javax.security.auth.spi.LoginModule; ++import org.apache.harmony.javax.security.auth.AuthPermission; ++ ++import org.apache.harmony.javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag; ++ ++ ++ ++public class LoginContext { ++ ++ private static final String DEFAULT_CALLBACK_HANDLER_PROPERTY = "auth.login.defaultCallbackHandler"; //$NON-NLS-1$ ++ ++ /* ++ * Integer constants which serve as a replacement for the corresponding ++ * LoginModuleControlFlag.* constants. These integers are used later as ++ * index in the arrays - see loginImpl() and logoutImpl() methods ++ */ ++ private static final int OPTIONAL = 0; ++ ++ private static final int REQUIRED = 1; ++ ++ private static final int REQUISITE = 2; ++ ++ private static final int SUFFICIENT = 3; ++ ++ // Subject to be used for this LoginContext's operations ++ private Subject subject; ++ ++ /* ++ * Shows whether the subject was specified by user (true) or was created by ++ * this LoginContext itself (false). ++ */ ++ private boolean userProvidedSubject; ++ ++ // Shows whether we use installed or user-provided Configuration ++ private boolean userProvidedConfig; ++ ++ // An user's AccessControlContext, used when user specifies ++ private AccessControlContext userContext; ++ ++ /* ++ * Either a callback handler passed by the user or a wrapper for the user's ++ * specified handler - see init() below. ++ */ ++ private CallbackHandler callbackHandler; ++ ++ /* ++ * An array which keeps the instantiated and init()-ialized login modules ++ * and their states ++ */ ++ private Module[] modules; ++ ++ // Stores a shared state ++ private Map sharedState; ++ ++ // A context class loader used to load [mainly] LoginModules ++ private ClassLoader contextClassLoader; ++ ++ // Shows overall status - whether this LoginContext was successfully logged ++ private boolean loggedIn; ++ ++ public LoginContext(String name) throws LoginException { ++ super(); ++ init(name, null, null, null); ++ } ++ ++ public LoginContext(String name, CallbackHandler cbHandler) throws LoginException { ++ super(); ++ if (cbHandler == null) { ++ throw new LoginException("auth.34"); //$NON-NLS-1$ ++ } ++ init(name, null, cbHandler, null); ++ } ++ ++ public LoginContext(String name, Subject subject) throws LoginException { ++ super(); ++ if (subject == null) { ++ throw new LoginException("auth.03"); //$NON-NLS-1$ ++ } ++ init(name, subject, null, null); ++ } ++ ++ public LoginContext(String name, Subject subject, CallbackHandler cbHandler) ++ throws LoginException { ++ super(); ++ if (subject == null) { ++ throw new LoginException("auth.03"); //$NON-NLS-1$ ++ } ++ if (cbHandler == null) { ++ throw new LoginException("auth.34"); //$NON-NLS-1$ ++ } ++ init(name, subject, cbHandler, null); ++ } ++ ++ public LoginContext(String name, Subject subject, CallbackHandler cbHandler, ++ Configuration config) throws LoginException { ++ super(); ++ init(name, subject, cbHandler, config); ++ } ++ ++ // Does all the machinery needed for the initialization. ++ private void init(String name, Subject subject, final CallbackHandler cbHandler, ++ Configuration config) throws LoginException { ++ userProvidedSubject = (this.subject = subject) != null; ++ ++ // ++ // Set config ++ // ++ if (name == null) { ++ throw new LoginException("auth.00"); //$NON-NLS-1$ ++ } ++ ++ if (config == null) { ++ config = Configuration.getAccessibleConfiguration(); ++ } else { ++ userProvidedConfig = true; ++ } ++ ++ SecurityManager sm = System.getSecurityManager(); ++ ++ if (sm != null && !userProvidedConfig) { ++ sm.checkPermission(new AuthPermission("createLoginContext." + name));//$NON-NLS-1$ ++ } ++ ++ AppConfigurationEntry[] entries = config.getAppConfigurationEntry(name); ++ if (entries == null) { ++ if (sm != null && !userProvidedConfig) { ++ sm.checkPermission(new AuthPermission("createLoginContext.other")); //$NON-NLS-1$ ++ } ++ entries = config.getAppConfigurationEntry("other"); //$NON-NLS-1$ ++ if (entries == null) { ++ throw new LoginException("auth.35 " + name); //$NON-NLS-1$ ++ } ++ } ++ ++ modules = new Module[entries.length]; ++ for (int i = 0; i < modules.length; i++) { ++ modules[i] = new Module(entries[i]); ++ } ++ // ++ // Set CallbackHandler and this.contextClassLoader ++ // ++ ++ /* ++ * as some of the operations to be executed (i.e. get*ClassLoader, ++ * getProperty, class loading) are security-checked, then combine all of ++ * them into a single doPrivileged() call. ++ */ ++ try { ++ AccessController.doPrivileged(new PrivilegedExceptionAction() { ++ public Void run() throws Exception { ++ // First, set the 'contextClassLoader' ++ contextClassLoader = Thread.currentThread().getContextClassLoader(); ++ if (contextClassLoader == null) { ++ contextClassLoader = ClassLoader.getSystemClassLoader(); ++ } ++ // then, checks whether the cbHandler is set ++ if (cbHandler == null) { ++ // well, let's try to find it ++ String klassName = Security ++ .getProperty(DEFAULT_CALLBACK_HANDLER_PROPERTY); ++ if (klassName == null || klassName.length() == 0) { ++ return null; ++ } ++ Class klass = Class.forName(klassName, true, contextClassLoader); ++ callbackHandler = (CallbackHandler) klass.newInstance(); ++ } else { ++ callbackHandler = cbHandler; ++ } ++ return null; ++ } ++ }); ++ } catch (PrivilegedActionException ex) { ++ Throwable cause = ex.getCause(); ++ throw (LoginException) new LoginException("auth.36").initCause(cause);//$NON-NLS-1$ ++ } ++ ++ if (userProvidedConfig) { ++ userContext = AccessController.getContext(); ++ } else if (callbackHandler != null) { ++ userContext = AccessController.getContext(); ++ callbackHandler = new ContextedCallbackHandler(callbackHandler); ++ } ++ } ++ ++ public Subject getSubject() { ++ if (userProvidedSubject || loggedIn) { ++ return subject; ++ } ++ return null; ++ } ++ ++ /** ++ * Warning: calling the method more than once may result in undefined ++ * behaviour if logout() method is not invoked before. ++ */ ++ public void login() throws LoginException { ++ PrivilegedExceptionAction action = new PrivilegedExceptionAction() { ++ public Void run() throws LoginException { ++ loginImpl(); ++ return null; ++ } ++ }; ++ try { ++ if (userProvidedConfig) { ++ AccessController.doPrivileged(action, userContext); ++ } else { ++ AccessController.doPrivileged(action); ++ } ++ } catch (PrivilegedActionException ex) { ++ throw (LoginException) ex.getException(); ++ } ++ } ++ ++ /** ++ * The real implementation of login() method whose calls are wrapped into ++ * appropriate doPrivileged calls in login(). ++ */ ++ private void loginImpl() throws LoginException { ++ if (subject == null) { ++ subject = new Subject(); ++ } ++ ++ if (sharedState == null) { ++ sharedState = new HashMap(); ++ } ++ ++ // PHASE 1: Calling login()-s ++ Throwable firstProblem = null; ++ ++ int[] logged = new int[4]; ++ int[] total = new int[4]; ++ ++ for (Module module : modules) { ++ try { ++ // if a module fails during Class.forName(), then it breaks overall ++ // attempt - see catch() below ++ module.create(subject, callbackHandler, sharedState); ++ ++ if (module.module.login()) { ++ ++total[module.getFlag()]; ++ ++logged[module.getFlag()]; ++ if (module.getFlag() == SUFFICIENT) { ++ break; ++ } ++ } ++ } catch (Throwable ex) { ++ if (firstProblem == null) { ++ firstProblem = ex; ++ } ++ if (module.klass == null) { ++ /* ++ * an exception occurred during class lookup - overall ++ * attempt must fail a little trick: increase the REQUIRED's ++ * number - this will look like a failed REQUIRED module ++ * later, so overall attempt will fail ++ */ ++ ++total[REQUIRED]; ++ break; ++ } ++ ++total[module.getFlag()]; ++ // something happened after the class was loaded ++ if (module.getFlag() == REQUISITE) { ++ // ... and no need to walk down anymore ++ break; ++ } ++ } ++ } ++ // end of PHASE1, ++ ++ // Let's decide whether we have either overall success or a total failure ++ boolean fail = true; ++ ++ /* ++ * Note: 'failed[xxx]!=0' is not enough to check. ++ * ++ * Use 'logged[xx] != total[xx]' instead. This is because some modules ++ * might not be counted as 'failed' if an exception occurred during ++ * preload()/Class.forName()-ing. But, such modules still get counted in ++ * the total[]. ++ */ ++ ++ // if any REQ* module failed - then it's failure ++ if (logged[REQUIRED] != total[REQUIRED] || logged[REQUISITE] != total[REQUISITE]) { ++ // fail = true; ++ } else { ++ if (total[REQUIRED] == 0 && total[REQUISITE] == 0) { ++ // neither REQUIRED nor REQUISITE was configured. ++ // must have at least one SUFFICIENT or OPTIONAL ++ if (logged[OPTIONAL] != 0 || logged[SUFFICIENT] != 0) { ++ fail = false; ++ } ++ //else { fail = true; } ++ } else { ++ fail = false; ++ } ++ } ++ ++ int commited[] = new int[4]; ++ // clear it ++ total[0] = total[1] = total[2] = total[3] = 0; ++ if (!fail) { ++ // PHASE 2: ++ ++ for (Module module : modules) { ++ if (module.klass != null) { ++ ++total[module.getFlag()]; ++ try { ++ module.module.commit(); ++ ++commited[module.getFlag()]; ++ } catch (Throwable ex) { ++ if (firstProblem == null) { ++ firstProblem = ex; ++ } ++ } ++ } ++ } ++ } ++ ++ // need to decide once again ++ fail = true; ++ if (commited[REQUIRED] != total[REQUIRED] || commited[REQUISITE] != total[REQUISITE]) { ++ //fail = true; ++ } else { ++ if (total[REQUIRED] == 0 && total[REQUISITE] == 0) { ++ /* ++ * neither REQUIRED nor REQUISITE was configured. must have at ++ * least one SUFFICIENT or OPTIONAL ++ */ ++ if (commited[OPTIONAL] != 0 || commited[SUFFICIENT] != 0) { ++ fail = false; ++ } else { ++ //fail = true; ++ } ++ } else { ++ fail = false; ++ } ++ } ++ ++ if (fail) { ++ // either login() or commit() failed. aborting... ++ ++ for (Module module : modules) { ++ try { ++ module.module.abort(); ++ } catch ( /*LoginException*/Throwable ex) { ++ if (firstProblem == null) { ++ firstProblem = ex; ++ } ++ } ++ } ++ if (firstProblem instanceof PrivilegedActionException ++ && firstProblem.getCause() != null) { ++ firstProblem = firstProblem.getCause(); ++ } ++ if (firstProblem instanceof LoginException) { ++ throw (LoginException) firstProblem; ++ } ++ throw (LoginException) new LoginException("auth.37").initCause(firstProblem); //$NON-NLS-1$ ++ } ++ loggedIn = true; ++ } ++ ++ public void logout() throws LoginException { ++ PrivilegedExceptionAction action = new PrivilegedExceptionAction() { ++ public Void run() throws LoginException { ++ logoutImpl(); ++ return null; ++ } ++ }; ++ try { ++ if (userProvidedConfig) { ++ AccessController.doPrivileged(action, userContext); ++ } else { ++ AccessController.doPrivileged(action); ++ } ++ } catch (PrivilegedActionException ex) { ++ throw (LoginException) ex.getException(); ++ } ++ } ++ ++ /** ++ * The real implementation of logout() method whose calls are wrapped into ++ * appropriate doPrivileged calls in logout(). ++ */ ++ private void logoutImpl() throws LoginException { ++ if (subject == null) { ++ throw new LoginException("auth.38"); //$NON-NLS-1$ ++ } ++ loggedIn = false; ++ Throwable firstProblem = null; ++ int total = 0; ++ for (Module module : modules) { ++ try { ++ module.module.logout(); ++ ++total; ++ } catch (Throwable ex) { ++ if (firstProblem == null) { ++ firstProblem = ex; ++ } ++ } ++ } ++ if (firstProblem != null || total == 0) { ++ if (firstProblem instanceof PrivilegedActionException ++ && firstProblem.getCause() != null) { ++ firstProblem = firstProblem.getCause(); ++ } ++ if (firstProblem instanceof LoginException) { ++ throw (LoginException) firstProblem; ++ } ++ throw (LoginException) new LoginException("auth.37").initCause(firstProblem); //$NON-NLS-1$ ++ } ++ } ++ ++ /** ++ *

A class that servers as a wrapper for the CallbackHandler when we use ++ * installed Configuration, but not a passed one. See API docs on the ++ * LoginContext.

++ * ++ *

Simply invokes the given handler with the given AccessControlContext.

++ */ ++ private class ContextedCallbackHandler implements CallbackHandler { ++ private final CallbackHandler hiddenHandlerRef; ++ ++ ContextedCallbackHandler(CallbackHandler handler) { ++ super(); ++ this.hiddenHandlerRef = handler; ++ } ++ ++ public void handle(final Callback[] callbacks) throws IOException, ++ UnsupportedCallbackException { ++ try { ++ AccessController.doPrivileged(new PrivilegedExceptionAction() { ++ public Void run() throws IOException, UnsupportedCallbackException { ++ hiddenHandlerRef.handle(callbacks); ++ return null; ++ } ++ }, userContext); ++ } catch (PrivilegedActionException ex) { ++ if (ex.getCause() instanceof UnsupportedCallbackException) { ++ throw (UnsupportedCallbackException) ex.getCause(); ++ } ++ throw (IOException) ex.getCause(); ++ } ++ } ++ } ++ ++ /** ++ * A private class that stores an instantiated LoginModule. ++ */ ++ private final class Module { ++ ++ // An initial info about the module to be used ++ AppConfigurationEntry entry; ++ ++ // A mapping of LoginModuleControlFlag onto a simple int constant ++ int flag; ++ ++ // The LoginModule itself ++ LoginModule module; ++ ++ // A class of the module ++ Class klass; ++ ++ Module(AppConfigurationEntry entry) { ++ this.entry = entry; ++ LoginModuleControlFlag flg = entry.getControlFlag(); ++ if (flg == LoginModuleControlFlag.OPTIONAL) { ++ flag = OPTIONAL; ++ } else if (flg == LoginModuleControlFlag.REQUISITE) { ++ flag = REQUISITE; ++ } else if (flg == LoginModuleControlFlag.SUFFICIENT) { ++ flag = SUFFICIENT; ++ } else { ++ flag = REQUIRED; ++ //if(flg!=LoginModuleControlFlag.REQUIRED) throw new Error() ++ } ++ } ++ ++ int getFlag() { ++ return flag; ++ } ++ ++ /** ++ * Loads class of the LoginModule, instantiates it and then calls ++ * initialize(). ++ */ ++ void create(Subject subject, CallbackHandler callbackHandler, Map sharedState) ++ throws LoginException { ++ String klassName = entry.getLoginModuleName(); ++ if (klass == null) { ++ try { ++ klass = Class.forName(klassName, false, contextClassLoader); ++ } catch (ClassNotFoundException ex) { ++ throw (LoginException) new LoginException( ++ "auth.39 " + klassName).initCause(ex); //$NON-NLS-1$ ++ } ++ } ++ ++ if (module == null) { ++ try { ++ module = (LoginModule) klass.newInstance(); ++ } catch (IllegalAccessException ex) { ++ throw (LoginException) new LoginException( ++ "auth.3A " + klassName) //$NON-NLS-1$ ++ .initCause(ex); ++ } catch (InstantiationException ex) { ++ throw (LoginException) new LoginException( ++ "auth.3A" + klassName) //$NON-NLS-1$ ++ .initCause(ex); ++ } ++ module.initialize(subject, callbackHandler, sharedState, entry.getOptions()); ++ } ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/LoginException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/LoginException.java +new file mode 100644 +index 0000000..e9ea566 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/LoginException.java +@@ -0,0 +1,45 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login; ++ ++import java.security.GeneralSecurityException; ++ ++/** ++ * Base class for exceptions that are thrown when a login error occurs. ++ */ ++public class LoginException extends GeneralSecurityException { ++ ++ private static final long serialVersionUID = -4679091624035232488L; ++ ++ /** ++ * Creates a new exception instance and initializes it with default values. ++ */ ++ public LoginException() { ++ super(); ++ } ++ ++ /** ++ * Creates a new exception instance and initializes it with a given message. ++ * ++ * @param message the error message ++ */ ++ public LoginException(String message) { ++ super(message); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/spi/LoginModule.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/spi/LoginModule.java +new file mode 100644 +index 0000000..3ed9eb2 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/spi/LoginModule.java +@@ -0,0 +1,38 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.auth.spi; ++ ++import java.util.Map; ++ ++import org.apache.harmony.javax.security.auth.Subject; ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++import org.apache.harmony.javax.security.auth.login.LoginException; ++ ++public interface LoginModule { ++ ++ void initialize(Subject subject, CallbackHandler callbackHandler, ++ Map sharedState, Map options); ++ ++ boolean login() throws LoginException; ++ ++ boolean commit() throws LoginException; ++ ++ boolean abort() throws LoginException; ++ ++ boolean logout() throws LoginException; ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/AuthenticationException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/AuthenticationException.java +new file mode 100644 +index 0000000..38703ef +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/AuthenticationException.java +@@ -0,0 +1,35 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.sasl; ++ ++public class AuthenticationException extends SaslException { ++ ++ private static final long serialVersionUID = -3579708765071815007L; ++ ++ public AuthenticationException() { ++ super(); ++ } ++ ++ public AuthenticationException(String detail) { ++ super(detail); ++ } ++ ++ public AuthenticationException(String detail, Throwable ex) { ++ super(detail, ex); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/AuthorizeCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/AuthorizeCallback.java +new file mode 100644 +index 0000000..2ba90a2 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/AuthorizeCallback.java +@@ -0,0 +1,79 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.sasl; ++ ++import java.io.Serializable; ++import org.apache.harmony.javax.security.auth.callback.Callback; ++ ++public class AuthorizeCallback implements Callback, Serializable { ++ ++ private static final long serialVersionUID = -2353344186490470805L; ++ ++ /** ++ * Serialized field for storing authenticationID. ++ */ ++ private final String authenticationID; ++ ++ /** ++ * Serialized field for storing authorizationID. ++ */ ++ private final String authorizationID; ++ ++ /** ++ * Serialized field for storing authorizedID. ++ */ ++ private String authorizedID; ++ ++ /** ++ * Store authorized Serialized field. ++ */ ++ private boolean authorized; ++ ++ public AuthorizeCallback(String authnID, String authzID) { ++ super(); ++ authenticationID = authnID; ++ authorizationID = authzID; ++ authorizedID = authzID; ++ } ++ ++ public String getAuthenticationID() { ++ return authenticationID; ++ } ++ ++ public String getAuthorizationID() { ++ return authorizationID; ++ } ++ ++ public String getAuthorizedID() { ++ return (authorized ? authorizedID : null); ++ } ++ ++ public boolean isAuthorized() { ++ return authorized; ++ } ++ ++ public void setAuthorized(boolean ok) { ++ authorized = ok; ++ } ++ ++ public void setAuthorizedID(String id) { ++ if (id != null) { ++ authorizedID = id; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/RealmCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/RealmCallback.java +new file mode 100644 +index 0000000..65b5d15 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/RealmCallback.java +@@ -0,0 +1,33 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.sasl; ++ ++import org.apache.harmony.javax.security.auth.callback.TextInputCallback; ++ ++public class RealmCallback extends TextInputCallback { ++ ++ private static final long serialVersionUID = -4342673378785456908L; ++ ++ public RealmCallback(String prompt) { ++ super(prompt); ++ } ++ ++ public RealmCallback(String prompt, String defaultRealmInfo) { ++ super(prompt, defaultRealmInfo); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/RealmChoiceCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/RealmChoiceCallback.java +new file mode 100644 +index 0000000..079ea07 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/RealmChoiceCallback.java +@@ -0,0 +1,30 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.sasl; ++ ++import org.apache.harmony.javax.security.auth.callback.ChoiceCallback; ++ ++public class RealmChoiceCallback extends ChoiceCallback { ++ ++ private static final long serialVersionUID = -8588141348846281332L; ++ ++ public RealmChoiceCallback(String prompt, String[] choices, int defaultChoice, ++ boolean multiple) { ++ super(prompt, choices, defaultChoice, multiple); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/Sasl.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/Sasl.java +new file mode 100644 +index 0000000..4d827f8 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/Sasl.java +@@ -0,0 +1,204 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.sasl; ++ ++import java.security.Provider; ++import java.security.Security; ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++ ++ ++ ++import java.util.Collection; ++import java.util.Collections; ++import java.util.Enumeration; ++import java.util.Map; ++import java.util.HashSet; ++import java.util.Iterator; ++ ++public class Sasl { ++ // SaslClientFactory service name ++ private static final String CLIENTFACTORYSRV = "SaslClientFactory"; //$NON-NLS-1$ ++ ++ // SaslServerFactory service name ++ private static final String SERVERFACTORYSRV = "SaslServerFactory"; //$NON-NLS-1$ ++ ++ public static final String POLICY_NOPLAINTEXT = "javax.security.sasl.policy.noplaintext"; //$NON-NLS-1$ ++ ++ public static final String POLICY_NOACTIVE = "javax.security.sasl.policy.noactive"; //$NON-NLS-1$ ++ ++ public static final String POLICY_NODICTIONARY = "javax.security.sasl.policy.nodictionary"; //$NON-NLS-1$ ++ ++ public static final String POLICY_NOANONYMOUS = "javax.security.sasl.policy.noanonymous"; //$NON-NLS-1$ ++ ++ public static final String POLICY_FORWARD_SECRECY = "javax.security.sasl.policy.forward"; //$NON-NLS-1$ ++ ++ public static final String POLICY_PASS_CREDENTIALS = "javax.security.sasl.policy.credentials"; //$NON-NLS-1$ ++ ++ public static final String MAX_BUFFER = "javax.security.sasl.maxbuffer"; //$NON-NLS-1$ ++ ++ public static final String RAW_SEND_SIZE = "javax.security.sasl.rawsendsize"; //$NON-NLS-1$ ++ ++ public static final String REUSE = "javax.security.sasl.reuse"; //$NON-NLS-1$ ++ ++ public static final String QOP = "javax.security.sasl.qop"; //$NON-NLS-1$ ++ ++ public static final String STRENGTH = "javax.security.sasl.strength"; //$NON-NLS-1$ ++ ++ public static final String SERVER_AUTH = "javax.security.sasl.server.authentication"; //$NON-NLS-1$ ++ ++ // Default public constructor is overridden ++ private Sasl() { ++ super(); ++ } ++ ++ // Forms new instance of factory ++ private static Object newInstance(String factoryName, Provider prv) throws SaslException { ++ String msg = "auth.31"; //$NON-NLS-1$ ++ Object factory; ++ ClassLoader cl = prv.getClass().getClassLoader(); ++ if (cl == null) { ++ cl = ClassLoader.getSystemClassLoader(); ++ } ++ try { ++ factory = (Class.forName(factoryName, true, cl)).newInstance(); ++ return factory; ++ } catch (IllegalAccessException e) { ++ throw new SaslException(msg + factoryName, e); ++ } catch (ClassNotFoundException e) { ++ throw new SaslException(msg + factoryName, e); ++ } catch (InstantiationException e) { ++ throw new SaslException(msg + factoryName, e); ++ } ++ } ++ ++ /** ++ * This method forms the list of SaslClient/SaslServer factories which are ++ * implemented in used providers ++ */ ++ private static Collection findFactories(String service) { ++ HashSet fact = new HashSet(); ++ Provider[] pp = Security.getProviders(); ++ if ((pp == null) || (pp.length == 0)) { ++ return fact; ++ } ++ HashSet props = new HashSet(); ++ for (int i = 0; i < pp.length; i++) { ++ String prName = pp[i].getName(); ++ Enumeration keys = pp[i].keys(); ++ while (keys.hasMoreElements()) { ++ String s = (String) keys.nextElement(); ++ if (s.startsWith(service)) { ++ String prop = pp[i].getProperty(s); ++ try { ++ if (props.add(prName.concat(prop))) { ++ fact.add(newInstance(prop, pp[i])); ++ } ++ } catch (SaslException e) { ++ // ignore this factory ++ e.printStackTrace(); ++ } ++ } ++ } ++ } ++ return fact; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public static Enumeration getSaslClientFactories() { ++ Collection res = (Collection) findFactories(CLIENTFACTORYSRV); ++ return Collections.enumeration(res); ++ ++ } ++ ++ @SuppressWarnings("unchecked") ++ public static Enumeration getSaslServerFactories() { ++ Collection res = (Collection) findFactories(SERVERFACTORYSRV); ++ return Collections.enumeration(res); ++ } ++ ++ public static SaslServer createSaslServer(String mechanism, String protocol, ++ String serverName, Map prop, CallbackHandler cbh) throws SaslException { ++ if (mechanism == null) { ++ throw new NullPointerException("auth.32"); //$NON-NLS-1$ ++ } ++ Collection res = findFactories(SERVERFACTORYSRV); ++ if (res.isEmpty()) { ++ return null; ++ } ++ ++ Iterator iter = res.iterator(); ++ while (iter.hasNext()) { ++ SaslServerFactory fact = (SaslServerFactory) iter.next(); ++ String[] mech = fact.getMechanismNames(null); ++ boolean is = false; ++ if (mech != null) { ++ for (int j = 0; j < mech.length; j++) { ++ if (mech[j].equals(mechanism)) { ++ is = true; ++ break; ++ } ++ } ++ } ++ if (is) { ++ SaslServer saslS = fact.createSaslServer(mechanism, protocol, serverName, prop, ++ cbh); ++ if (saslS != null) { ++ return saslS; ++ } ++ } ++ } ++ return null; ++ } ++ ++ public static SaslClient createSaslClient(String[] mechanisms, String authanticationID, ++ String protocol, String serverName, Map prop, CallbackHandler cbh) ++ throws SaslException { ++ if (mechanisms == null) { ++ throw new NullPointerException("auth.33"); //$NON-NLS-1$ ++ } ++ Collection res = findFactories(CLIENTFACTORYSRV); ++ if (res.isEmpty()) { ++ return null; ++ } ++ ++ Iterator iter = res.iterator(); ++ while (iter.hasNext()) { ++ SaslClientFactory fact = (SaslClientFactory) iter.next(); ++ String[] mech = fact.getMechanismNames(null); ++ boolean is = false; ++ if (mech != null) { ++ for (int j = 0; j < mech.length; j++) { ++ for (int n = 0; n < mechanisms.length; n++) { ++ if (mech[j].equals(mechanisms[n])) { ++ is = true; ++ break; ++ } ++ } ++ } ++ } ++ if (is) { ++ SaslClient saslC = fact.createSaslClient(mechanisms, authanticationID, ++ protocol, serverName, prop, cbh); ++ if (saslC != null) { ++ return saslC; ++ } ++ } ++ } ++ return null; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslClient.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslClient.java +new file mode 100644 +index 0000000..e07ff53 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslClient.java +@@ -0,0 +1,37 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.sasl; ++ ++public interface SaslClient { ++ ++ void dispose() throws SaslException; ++ ++ byte[] evaluateChallenge(byte[] challenge) throws SaslException; ++ ++ String getMechanismName(); ++ ++ Object getNegotiatedProperty(String propName); ++ ++ boolean hasInitialResponse(); ++ ++ boolean isComplete(); ++ ++ byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException; ++ ++ byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException; ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslClientFactory.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslClientFactory.java +new file mode 100644 +index 0000000..e567ed3 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslClientFactory.java +@@ -0,0 +1,30 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.sasl; ++ ++import java.util.Map; ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++ ++public interface SaslClientFactory { ++ ++ SaslClient createSaslClient(String[] mechanisms, String authorizationId, String protocol, ++ String serverName, Map props, CallbackHandler cbh) throws SaslException; ++ ++ String[] getMechanismNames(Map props); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslException.java +new file mode 100644 +index 0000000..1ab7b12 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslException.java +@@ -0,0 +1,69 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.sasl; ++ ++import java.io.IOException; ++ ++public class SaslException extends IOException { ++ ++ private static final long serialVersionUID = 4579784287983423626L; ++ ++ /** ++ * Serialized field for storing initial cause ++ */ ++ private Throwable _exception; ++ ++ public SaslException() { ++ super(); ++ } ++ ++ public SaslException(String detail) { ++ super(detail); ++ } ++ ++ public SaslException(String detail, Throwable ex) { ++ super(detail); ++ if (ex != null) { ++ super.initCause(ex); ++ _exception = ex; ++ } ++ } ++ ++ @Override ++ public Throwable getCause() { ++ return _exception; ++ } ++ ++ @Override ++ public Throwable initCause(Throwable cause) { ++ super.initCause(cause); ++ _exception = cause; ++ return this; ++ } ++ ++ @Override ++ public String toString() { ++ if (_exception == null) { ++ return super.toString(); ++ } ++ StringBuilder sb = new StringBuilder(super.toString()); ++ sb.append(", caused by: "); //$NON-NLS-1$ ++ sb.append(_exception.toString()); ++ return sb.toString(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslServer.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslServer.java +new file mode 100644 +index 0000000..f057a4b +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslServer.java +@@ -0,0 +1,37 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.sasl; ++ ++public interface SaslServer { ++ ++ void dispose() throws SaslException; ++ ++ byte[] evaluateResponse(byte[] response) throws SaslException; ++ ++ String getAuthorizationID(); ++ ++ String getMechanismName(); ++ ++ Object getNegotiatedProperty(String propName); ++ ++ boolean isComplete(); ++ ++ byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException; ++ ++ byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException; ++} +diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslServerFactory.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslServerFactory.java +new file mode 100644 +index 0000000..d59530e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslServerFactory.java +@@ -0,0 +1,30 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You 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.apache.harmony.javax.security.sasl; ++ ++import java.util.Map; ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++ ++public interface SaslServerFactory { ++ ++ SaslServer createSaslServer(String mechanisms, String protocol, String serverName, ++ Map props, CallbackHandler cbh) throws SaslException; ++ ++ String[] getMechanismNames(Map props); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/CRAMMD5HashedSaslClientFactory.java b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/CRAMMD5HashedSaslClientFactory.java +new file mode 100644 +index 0000000..5c33e40 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/CRAMMD5HashedSaslClientFactory.java +@@ -0,0 +1,59 @@ ++/* ++ * ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you 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.apache.qpid.management.common.sasl; ++ ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++import de.measite.smack.Sasl; ++import org.apache.harmony.javax.security.sasl.SaslClient; ++import org.apache.harmony.javax.security.sasl.SaslClientFactory; ++import org.apache.harmony.javax.security.sasl.SaslException; ++import java.util.Map; ++ ++public class CRAMMD5HashedSaslClientFactory implements SaslClientFactory ++{ ++ /** The name of this mechanism */ ++ public static final String MECHANISM = "CRAM-MD5-HASHED"; ++ ++ public SaslClient createSaslClient(String[] mechanisms, String authorizationId, String protocol, ++ String serverName, Map props, CallbackHandler cbh) ++ throws SaslException ++ { ++ for (int i = 0; i < mechanisms.length; i++) ++ { ++ if (mechanisms[i].equals(MECHANISM)) ++ { ++ if (cbh == null) ++ { ++ throw new SaslException("CallbackHandler must not be null"); ++ } ++ ++ String[] mechs = {"CRAM-MD5"}; ++ return Sasl.createSaslClient(mechs, authorizationId, protocol, serverName, props, cbh); ++ } ++ } ++ return null; ++ } ++ ++ public String[] getMechanismNames(Map props) ++ { ++ return new String[]{MECHANISM}; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/ClientSaslFactory.java b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/ClientSaslFactory.java +new file mode 100644 +index 0000000..19162d8 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/ClientSaslFactory.java +@@ -0,0 +1,53 @@ ++/* ++ * ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you 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.apache.qpid.management.common.sasl; ++ ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++import org.apache.harmony.javax.security.sasl.SaslClient; ++import org.apache.harmony.javax.security.sasl.SaslClientFactory; ++import org.apache.harmony.javax.security.sasl.SaslException; ++import java.util.Map; ++ ++public class ClientSaslFactory implements SaslClientFactory ++{ ++ public SaslClient createSaslClient(String[] mechs, String authorizationId, String protocol, ++ String serverName, Map props, CallbackHandler cbh) ++ throws SaslException ++ { ++ for (int i = 0; i < mechs.length; i++) ++ { ++ if (mechs[i].equals("PLAIN")) ++ { ++ return new PlainSaslClient(authorizationId, cbh); ++ } ++ } ++ return null; ++ } ++ ++ /** ++ * Simple-minded implementation that ignores props ++ */ ++ public String[] getMechanismNames(Map props) ++ { ++ return new String[]{"PLAIN"}; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/Constants.java b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/Constants.java +new file mode 100644 +index 0000000..31010ba +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/Constants.java +@@ -0,0 +1,33 @@ ++/* ++ * ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you 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.apache.qpid.management.common.sasl; ++ ++public class Constants ++{ ++ ++ public final static String MECH_CRAMMD5 = "CRAM-MD5"; ++ public final static String MECH_PLAIN = "PLAIN"; ++ public final static String SASL_CRAMMD5 = "SASL/CRAM-MD5"; ++ public final static String SASL_PLAIN = "SASL/PLAIN"; ++ ++} ++ +diff --git a/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/JCAProvider.java b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/JCAProvider.java +new file mode 100644 +index 0000000..5793dae +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/JCAProvider.java +@@ -0,0 +1,55 @@ ++/* ++ * ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you 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.apache.qpid.management.common.sasl; ++ ++import org.apache.harmony.javax.security.sasl.SaslClientFactory; ++import java.security.Provider; ++import java.util.Map; ++ ++public class JCAProvider extends Provider ++{ ++ private static final long serialVersionUID = 1L; ++ ++ /** ++ * Creates the security provider with a map from SASL mechanisms to implementing factories. ++ * ++ * @param providerMap The map from SASL mechanims to implementing factory classes. ++ */ ++ public JCAProvider(Map> providerMap) ++ { ++ super("AMQSASLProvider", 1.0, "A JCA provider that registers all " ++ + "AMQ SASL providers that want to be registered"); ++ register(providerMap); ++ } ++ ++ /** ++ * Registers client factory classes for a map of mechanism names to client factory classes. ++ * ++ * @param providerMap The map from SASL mechanims to implementing factory classes. ++ */ ++ private void register(Map> providerMap) ++ { ++ for (Map.Entry> me : providerMap.entrySet()) ++ { ++ put("SaslClientFactory." + me.getKey(), me.getValue().getName()); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/PlainSaslClient.java b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/PlainSaslClient.java +new file mode 100644 +index 0000000..99a1d43 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/PlainSaslClient.java +@@ -0,0 +1,210 @@ ++/* ++ * ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you 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.apache.qpid.management.common.sasl; ++ ++import org.apache.harmony.javax.security.auth.callback.Callback; ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++import org.apache.harmony.javax.security.auth.callback.NameCallback; ++import org.apache.harmony.javax.security.auth.callback.PasswordCallback; ++import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException; ++import de.measite.smack.Sasl; ++import org.apache.harmony.javax.security.sasl.SaslClient; ++import org.apache.harmony.javax.security.sasl.SaslException; ++import java.io.IOException; ++import java.io.UnsupportedEncodingException; ++ ++public class PlainSaslClient implements SaslClient ++{ ++ ++ private boolean completed; ++ private CallbackHandler cbh; ++ private String authorizationID; ++ private String authenticationID; ++ private byte password[]; ++ private static byte SEPARATOR = 0; ++ ++ public PlainSaslClient(String authorizationID, CallbackHandler cbh) throws SaslException ++ { ++ completed = false; ++ this.cbh = cbh; ++ Object[] userInfo = getUserInfo(); ++ this.authorizationID = authorizationID; ++ this.authenticationID = (String) userInfo[0]; ++ this.password = (byte[]) userInfo[1]; ++ if (authenticationID == null || password == null) ++ { ++ throw new SaslException("PLAIN: authenticationID and password must be specified"); ++ } ++ } ++ ++ public byte[] evaluateChallenge(byte[] challenge) throws SaslException ++ { ++ if (completed) ++ { ++ throw new IllegalStateException("PLAIN: authentication already " + ++ "completed"); ++ } ++ completed = true; ++ try ++ { ++ byte authzid[] = ++ authorizationID == null ? null : authorizationID.getBytes("UTF8"); ++ byte authnid[] = authenticationID.getBytes("UTF8"); ++ byte response[] = ++ new byte[ ++ password.length + ++ authnid.length + ++ 2 + // SEPARATOR ++ (authzid != null ? authzid.length : 0) ++ ]; ++ int size = 0; ++ if (authzid != null) { ++ System.arraycopy(authzid, 0, response, 0, authzid.length); ++ size = authzid.length; ++ } ++ response[size++] = SEPARATOR; ++ System.arraycopy(authnid, 0, response, size, authnid.length); ++ size += authnid.length; ++ response[size++] = SEPARATOR; ++ System.arraycopy(password, 0, response, size, password.length); ++ clearPassword(); ++ return response; ++ } catch (UnsupportedEncodingException e) { ++ throw new SaslException("PLAIN: Cannot get UTF-8 encoding of ids", ++ e); ++ } ++ } ++ ++ public String getMechanismName() ++ { ++ return "PLAIN"; ++ } ++ ++ public boolean hasInitialResponse() ++ { ++ return true; ++ } ++ ++ public boolean isComplete() ++ { ++ return completed; ++ } ++ ++ public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException ++ { ++ if (completed) { ++ throw new IllegalStateException("PLAIN: this mechanism supports " + ++ "neither integrity nor privacy"); ++ } else { ++ throw new IllegalStateException("PLAIN: authentication not " + ++ "completed"); ++ } ++ } ++ ++ public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException ++ { ++ if (completed) ++ { ++ throw new IllegalStateException("PLAIN: this mechanism supports " + ++ "neither integrity nor privacy"); ++ } ++ else ++ { ++ throw new IllegalStateException("PLAIN: authentication not " + ++ "completed"); ++ } ++ } ++ ++ public Object getNegotiatedProperty(String propName) ++ { ++ if (completed) ++ { ++ if (propName.equals(Sasl.QOP)) ++ { ++ return "auth"; ++ } ++ else ++ { ++ return null; ++ } ++ } ++ else ++ { ++ throw new IllegalStateException("PLAIN: authentication not " + ++ "completed"); ++ } ++ } ++ ++ private void clearPassword() ++ { ++ if (password != null) ++ { ++ for (int i = 0 ; i < password.length ; i++) ++ { ++ password[i] = 0; ++ } ++ password = null; ++ } ++ } ++ ++ public void dispose() throws SaslException ++ { ++ clearPassword(); ++ } ++ ++ protected void finalize() ++ { ++ clearPassword(); ++ } ++ ++ private Object[] getUserInfo() throws SaslException ++ { ++ try ++ { ++ final String userPrompt = "PLAIN authentication id: "; ++ final String pwPrompt = "PLAIN password: "; ++ NameCallback nameCb = new NameCallback(userPrompt); ++ PasswordCallback passwordCb = new PasswordCallback(pwPrompt, false); ++ cbh.handle(new Callback[] { nameCb, passwordCb }); ++ String userid = nameCb.getName(); ++ char pwchars[] = passwordCb.getPassword(); ++ byte pwbytes[]; ++ if (pwchars != null) ++ { ++ pwbytes = (new String(pwchars)).getBytes("UTF8"); ++ passwordCb.clearPassword(); ++ } ++ else ++ { ++ pwbytes = null; ++ } ++ return (new Object[] { userid, pwbytes }); ++ } ++ catch (IOException e) ++ { ++ throw new SaslException("Cannot get password", e); ++ } ++ catch (UnsupportedCallbackException e) ++ { ++ throw new SaslException("Cannot get userid/password", e); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/SaslProvider.java b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/SaslProvider.java +new file mode 100644 +index 0000000..1eb44e3 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/SaslProvider.java +@@ -0,0 +1,35 @@ ++/* ++ * ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you 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.apache.qpid.management.common.sasl; ++ ++import java.security.Provider; ++ ++public class SaslProvider extends Provider ++{ ++ private static final long serialVersionUID = -6978096016899676466L; ++ ++ public SaslProvider() ++ { ++ super("SaslClientFactory", 1.0, "SASL PLAIN CLIENT MECHANISM"); ++ put("SaslClientFactory.PLAIN", "ClientSaslFactory"); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/UserPasswordCallbackHandler.java b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/UserPasswordCallbackHandler.java +new file mode 100644 +index 0000000..a7886cf +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/UserPasswordCallbackHandler.java +@@ -0,0 +1,77 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you 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.apache.qpid.management.common.sasl; ++ ++import org.apache.harmony.javax.security.auth.callback.Callback; ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++import org.apache.harmony.javax.security.auth.callback.NameCallback; ++import org.apache.harmony.javax.security.auth.callback.PasswordCallback; ++import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException; ++import java.io.IOException; ++ ++public class UserPasswordCallbackHandler implements CallbackHandler ++{ ++ private String user; ++ private char[] pwchars; ++ ++ public UserPasswordCallbackHandler(String user, String password) ++ { ++ this.user = user; ++ this.pwchars = password.toCharArray(); ++ } ++ ++ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException ++ { ++ for (int i = 0; i < callbacks.length; i++) ++ { ++ if (callbacks[i] instanceof NameCallback) ++ { ++ NameCallback ncb = (NameCallback) callbacks[i]; ++ ncb.setName(user); ++ } ++ else if (callbacks[i] instanceof PasswordCallback) ++ { ++ PasswordCallback pcb = (PasswordCallback) callbacks[i]; ++ pcb.setPassword(pwchars); ++ } ++ else ++ { ++ throw new UnsupportedCallbackException(callbacks[i]); ++ } ++ } ++ } ++ ++ private void clearPassword() ++ { ++ if (pwchars != null) ++ { ++ for (int i = 0 ; i < pwchars.length ; i++) ++ { ++ pwchars[i] = 0; ++ } ++ pwchars = null; ++ } ++ } ++ ++ protected void finalize() ++ { ++ clearPassword(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/UsernameHashedPasswordCallbackHandler.java b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/UsernameHashedPasswordCallbackHandler.java +new file mode 100644 +index 0000000..54d7374 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/UsernameHashedPasswordCallbackHandler.java +@@ -0,0 +1,107 @@ ++/* ++ * ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you 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.apache.qpid.management.common.sasl; ++ ++import org.apache.harmony.javax.security.auth.callback.Callback; ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++import org.apache.harmony.javax.security.auth.callback.NameCallback; ++import org.apache.harmony.javax.security.auth.callback.PasswordCallback; ++import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException; ++import java.io.IOException; ++import java.io.UnsupportedEncodingException; ++import java.security.MessageDigest; ++import java.security.NoSuchAlgorithmException; ++ ++ ++public class UsernameHashedPasswordCallbackHandler implements CallbackHandler ++{ ++ private String user; ++ private char[] pwchars; ++ ++ public UsernameHashedPasswordCallbackHandler(String user, String password) throws Exception ++ { ++ this.user = user; ++ this.pwchars = getHash(password); ++ } ++ ++ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException ++ { ++ for (int i = 0; i < callbacks.length; i++) ++ { ++ if (callbacks[i] instanceof NameCallback) ++ { ++ NameCallback ncb = (NameCallback) callbacks[i]; ++ ncb.setName(user); ++ } ++ else if (callbacks[i] instanceof PasswordCallback) ++ { ++ PasswordCallback pcb = (PasswordCallback) callbacks[i]; ++ pcb.setPassword(pwchars); ++ } ++ else ++ { ++ throw new UnsupportedCallbackException(callbacks[i]); ++ } ++ } ++ } ++ ++ ++ private void clearPassword() ++ { ++ if (pwchars != null) ++ { ++ for (int i = 0 ; i < pwchars.length ; i++) ++ { ++ pwchars[i] = 0; ++ } ++ pwchars = null; ++ } ++ } ++ ++ protected void finalize() ++ { ++ clearPassword(); ++ } ++ ++ public static char[] getHash(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException ++ { ++ byte[] data = text.getBytes("utf-8"); ++ ++ MessageDigest md = MessageDigest.getInstance("MD5"); ++ ++ for (byte b : data) ++ { ++ md.update(b); ++ } ++ ++ byte[] digest = md.digest(); ++ ++ char[] hash = new char[digest.length ]; ++ ++ int index = 0; ++ for (byte b : digest) ++ { ++ hash[index++] = (char) b; ++ } ++ ++ return hash; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/AbstractConnectionListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/AbstractConnectionListener.java +new file mode 100644 +index 0000000..69acf90 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/AbstractConnectionListener.java +@@ -0,0 +1,46 @@ ++/** ++ * All rights reserved. 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; ++ ++/** ++ * The AbstractConnectionListener class provides an empty implementation for all ++ * methods defined by the {@link ConnectionListener} interface. This is a ++ * convenience class which should be used in case you do not need to implement ++ * all methods. ++ * ++ * @author Henning Staib ++ */ ++public class AbstractConnectionListener implements ConnectionListener { ++ ++ public void connectionClosed() { ++ // do nothing ++ } ++ ++ public void connectionClosedOnError(Exception e) { ++ // do nothing ++ } ++ ++ public void reconnectingIn(int seconds) { ++ // do nothing ++ } ++ ++ public void reconnectionFailed(Exception e) { ++ // do nothing ++ } ++ ++ public void reconnectionSuccessful() { ++ // do nothing ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/AccountManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/AccountManager.java +new file mode 100644 +index 0000000..4d9faa5 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/AccountManager.java +@@ -0,0 +1,337 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.filter.AndFilter; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Registration; ++import org.jivesoftware.smack.util.StringUtils; ++ ++import java.util.Collection; ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++ ++/** ++ * Allows creation and management of accounts on an XMPP server. ++ * ++ * @see Connection#getAccountManager() ++ * @author Matt Tucker ++ */ ++public class AccountManager { ++ ++ private Connection connection; ++ private Registration info = null; ++ ++ /** ++ * Flag that indicates whether the server supports In-Band Registration. ++ * In-Band Registration may be advertised as a stream feature. If no stream feature ++ * was advertised from the server then try sending an IQ packet to discover if In-Band ++ * Registration is available. ++ */ ++ private boolean accountCreationSupported = false; ++ ++ /** ++ * Creates a new AccountManager instance. ++ * ++ * @param connection a connection to a XMPP server. ++ */ ++ public AccountManager(Connection connection) { ++ this.connection = connection; ++ } ++ ++ /** ++ * Sets whether the server supports In-Band Registration. In-Band Registration may be ++ * advertised as a stream feature. If no stream feature was advertised from the server ++ * then try sending an IQ packet to discover if In-Band Registration is available. ++ * ++ * @param accountCreationSupported true if the server supports In-Band Registration. ++ */ ++ void setSupportsAccountCreation(boolean accountCreationSupported) { ++ this.accountCreationSupported = accountCreationSupported; ++ } ++ ++ /** ++ * Returns true if the server supports creating new accounts. Many servers require ++ * that you not be currently authenticated when creating new accounts, so the safest ++ * behavior is to only create new accounts before having logged in to a server. ++ * ++ * @return true if the server support creating new accounts. ++ */ ++ public boolean supportsAccountCreation() { ++ // Check if we already know that the server supports creating new accounts ++ if (accountCreationSupported) { ++ return true; ++ } ++ // No information is known yet (e.g. no stream feature was received from the server ++ // indicating that it supports creating new accounts) so send an IQ packet as a way ++ // to discover if this feature is supported ++ try { ++ if (info == null) { ++ getRegistrationInfo(); ++ accountCreationSupported = info.getType() != IQ.Type.ERROR; ++ } ++ return accountCreationSupported; ++ } ++ catch (XMPPException xe) { ++ return false; ++ } ++ } ++ ++ /** ++ * Returns an unmodifiable collection of the names of the required account attributes. ++ * All attributes must be set when creating new accounts. The standard set of possible ++ * attributes are as follows:
    ++ *
  • name -- the user's name. ++ *
  • first -- the user's first name. ++ *
  • last -- the user's last name. ++ *
  • email -- the user's email address. ++ *
  • city -- the user's city. ++ *
  • state -- the user's state. ++ *
  • zip -- the user's ZIP code. ++ *
  • phone -- the user's phone number. ++ *
  • url -- the user's website. ++ *
  • date -- the date the registration took place. ++ *
  • misc -- other miscellaneous information to associate with the account. ++ *
  • text -- textual information to associate with the account. ++ *
  • remove -- empty flag to remove account. ++ *

++ * ++ * Typically, servers require no attributes when creating new accounts, or just ++ * the user's email address. ++ * ++ * @return the required account attributes. ++ */ ++ public Collection getAccountAttributes() { ++ try { ++ if (info == null) { ++ getRegistrationInfo(); ++ } ++ Map attributes = info.getAttributes(); ++ if (attributes != null) { ++ return Collections.unmodifiableSet(attributes.keySet()); ++ } ++ } ++ catch (XMPPException xe) { ++ xe.printStackTrace(); ++ } ++ return Collections.emptySet(); ++ } ++ ++ /** ++ * Returns the value of a given account attribute or null if the account ++ * attribute wasn't found. ++ * ++ * @param name the name of the account attribute to return its value. ++ * @return the value of the account attribute or null if an account ++ * attribute wasn't found for the requested name. ++ */ ++ public String getAccountAttribute(String name) { ++ try { ++ if (info == null) { ++ getRegistrationInfo(); ++ } ++ return info.getAttributes().get(name); ++ } ++ catch (XMPPException xe) { ++ xe.printStackTrace(); ++ } ++ return null; ++ } ++ ++ /** ++ * Returns the instructions for creating a new account, or null if there ++ * are no instructions. If present, instructions should be displayed to the end-user ++ * that will complete the registration process. ++ * ++ * @return the account creation instructions, or null if there are none. ++ */ ++ public String getAccountInstructions() { ++ try { ++ if (info == null) { ++ getRegistrationInfo(); ++ } ++ return info.getInstructions(); ++ } ++ catch (XMPPException xe) { ++ return null; ++ } ++ } ++ ++ /** ++ * Creates a new account using the specified username and password. The server may ++ * require a number of extra account attributes such as an email address and phone ++ * number. In that case, Smack will attempt to automatically set all required ++ * attributes with blank values, which may or may not be accepted by the server. ++ * Therefore, it's recommended to check the required account attributes and to let ++ * the end-user populate them with real values instead. ++ * ++ * @param username the username. ++ * @param password the password. ++ * @throws XMPPException if an error occurs creating the account. ++ */ ++ public void createAccount(String username, String password) throws XMPPException { ++ if (!supportsAccountCreation()) { ++ throw new XMPPException("Server does not support account creation."); ++ } ++ // Create a map for all the required attributes, but give them blank values. ++ Map attributes = new HashMap(); ++ for (String attributeName : getAccountAttributes()) { ++ attributes.put(attributeName, ""); ++ } ++ createAccount(username, password, attributes); ++ } ++ ++ /** ++ * Creates a new account using the specified username, password and account attributes. ++ * The attributes Map must contain only String name/value pairs and must also have values ++ * for all required attributes. ++ * ++ * @param username the username. ++ * @param password the password. ++ * @param attributes the account attributes. ++ * @throws XMPPException if an error occurs creating the account. ++ * @see #getAccountAttributes() ++ */ ++ public void createAccount(String username, String password, Map attributes) ++ throws XMPPException ++ { ++ if (!supportsAccountCreation()) { ++ throw new XMPPException("Server does not support account creation."); ++ } ++ Registration reg = new Registration(); ++ reg.setType(IQ.Type.SET); ++ reg.setTo(connection.getServiceName()); ++ attributes.put("username",username); ++ attributes.put("password",password); ++ reg.setAttributes(attributes); ++ PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()), ++ new PacketTypeFilter(IQ.class)); ++ PacketCollector collector = connection.createPacketCollector(filter); ++ connection.sendPacket(reg); ++ IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ collector.cancel(); ++ if (result == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (result.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(result.getError()); ++ } ++ } ++ ++ /** ++ * Changes the password of the currently logged-in account. This operation can only ++ * be performed after a successful login operation has been completed. Not all servers ++ * support changing passwords; an XMPPException will be thrown when that is the case. ++ * ++ * @throws IllegalStateException if not currently logged-in to the server. ++ * @throws XMPPException if an error occurs when changing the password. ++ */ ++ public void changePassword(String newPassword) throws XMPPException { ++ Registration reg = new Registration(); ++ reg.setType(IQ.Type.SET); ++ reg.setTo(connection.getServiceName()); ++ Map map = new HashMap(); ++ map.put("username",StringUtils.parseName(connection.getUser())); ++ map.put("password",newPassword); ++ reg.setAttributes(map); ++ PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()), ++ new PacketTypeFilter(IQ.class)); ++ PacketCollector collector = connection.createPacketCollector(filter); ++ connection.sendPacket(reg); ++ IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ collector.cancel(); ++ if (result == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (result.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(result.getError()); ++ } ++ } ++ ++ /** ++ * Deletes the currently logged-in account from the server. This operation can only ++ * be performed after a successful login operation has been completed. Not all servers ++ * support deleting accounts; an XMPPException will be thrown when that is the case. ++ * ++ * @throws IllegalStateException if not currently logged-in to the server. ++ * @throws XMPPException if an error occurs when deleting the account. ++ */ ++ public void deleteAccount() throws XMPPException { ++ if (!connection.isAuthenticated()) { ++ throw new IllegalStateException("Must be logged in to delete a account."); ++ } ++ Registration reg = new Registration(); ++ reg.setType(IQ.Type.SET); ++ reg.setTo(connection.getServiceName()); ++ Map attributes = new HashMap(); ++ // To delete an account, we add a single attribute, "remove", that is blank. ++ attributes.put("remove", ""); ++ reg.setAttributes(attributes); ++ PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()), ++ new PacketTypeFilter(IQ.class)); ++ PacketCollector collector = connection.createPacketCollector(filter); ++ connection.sendPacket(reg); ++ IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ collector.cancel(); ++ if (result == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (result.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(result.getError()); ++ } ++ } ++ ++ /** ++ * Gets the account registration info from the server. ++ * ++ * @throws XMPPException if an error occurs. ++ */ ++ private synchronized void getRegistrationInfo() throws XMPPException { ++ Registration reg = new Registration(); ++ reg.setTo(connection.getServiceName()); ++ PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()), ++ new PacketTypeFilter(IQ.class)); ++ PacketCollector collector = connection.createPacketCollector(filter); ++ connection.sendPacket(reg); ++ IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ collector.cancel(); ++ if (result == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (result.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(result.getError()); ++ } ++ else { ++ info = (Registration)result; ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/AndroidConnectionConfiguration.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/AndroidConnectionConfiguration.java +new file mode 100644 +index 0000000..a4cbacc +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/AndroidConnectionConfiguration.java +@@ -0,0 +1,86 @@ ++package org.jivesoftware.smack; ++ ++import org.jivesoftware.smack.proxy.ProxyInfo; ++import org.jivesoftware.smack.util.DNSUtil; ++ ++/** ++ * This class wraps DNS SRV lookups for a new ConnectionConfiguration in a ++ * new thread, since Android API >= 11 (Honeycomb) does not allow network ++ * activity in the main thread. ++ * ++ * @author Florian Schmaus fschmaus@gmail.com ++ * ++ */ ++public class AndroidConnectionConfiguration extends ConnectionConfiguration { ++ private static final int DEFAULT_TIMEOUT = 1000; ++ ++ /** ++ * Creates a new ConnectionConfiguration for the specified service name. ++ * A DNS SRV lookup will be performed to find out the actual host address ++ * and port to use for the connection. ++ * ++ * @param serviceName the name of the service provided by an XMPP server. ++ */ ++ public AndroidConnectionConfiguration(String serviceName) throws XMPPException { ++ super(); ++ AndroidInit(serviceName, DEFAULT_TIMEOUT); ++ } ++ ++ /** ++ * ++ * @param serviceName ++ * @param timeout ++ * @throws XMPPException ++ */ ++ public AndroidConnectionConfiguration(String serviceName, int timeout) throws XMPPException { ++ super(); ++ AndroidInit(serviceName, timeout); ++ } ++ ++ /** ++ * ++ * @param serviceName ++ * @param timeout ++ * @throws XMPPException ++ */ ++ private void AndroidInit(String serviceName, int timeout) throws XMPPException { ++ class DnsSrvLookupRunnable implements Runnable { ++ String serviceName; ++ volatile DNSUtil.HostAddress address; ++ ++ public DnsSrvLookupRunnable(String serviceName) { ++ this.serviceName = serviceName; ++ } ++ ++ @Override ++ public void run() { ++ // Perform DNS lookup to get host and port to use ++ address = DNSUtil.resolveXMPPDomain(serviceName); ++ } ++ ++ public DNSUtil.HostAddress getHostAddress() { ++ return address; ++ } ++ } ++ ++ DnsSrvLookupRunnable dnsSrv = new DnsSrvLookupRunnable(serviceName); ++ Thread t = new Thread(dnsSrv, "dns-srv-lookup"); ++ t.start(); ++ try { ++ t.join(timeout); ++ } catch (InterruptedException e) { ++ throw new XMPPException("DNS lookup timeout after " + timeout + "ms", e); ++ } ++ ++ DNSUtil.HostAddress address = dnsSrv.getHostAddress(); ++ if (address == null) { ++ throw new XMPPException("DNS lookup failure"); ++ } ++ ++ String host = address.getHost(); ++ int port = address.getPort(); ++ ProxyInfo proxy = ProxyInfo.forDefaultProxy(); ++ ++ init(host, port, serviceName, proxy); ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/BOSHConfiguration.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/BOSHConfiguration.java +new file mode 100644 +index 0000000..0b033b4 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/BOSHConfiguration.java +@@ -0,0 +1,124 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2009 Jive Software. ++ * ++ * All rights reserved. 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.net.URI; ++import java.net.URISyntaxException; ++ ++import org.jivesoftware.smack.ConnectionConfiguration; ++import org.jivesoftware.smack.proxy.ProxyInfo; ++ ++/** ++ * Configuration to use while establishing the connection to the XMPP server via ++ * HTTP binding. ++ * ++ * @see BOSHConnection ++ * @author Guenther Niess ++ */ ++public class BOSHConfiguration extends ConnectionConfiguration { ++ ++ private boolean ssl; ++ private String file; ++ ++ public BOSHConfiguration(String xmppDomain) { ++ super(xmppDomain, 7070); ++ setSASLAuthenticationEnabled(true); ++ ssl = false; ++ file = "/http-bind/"; ++ } ++ ++ public BOSHConfiguration(String xmppDomain, int port) { ++ super(xmppDomain, port); ++ setSASLAuthenticationEnabled(true); ++ ssl = false; ++ file = "/http-bind/"; ++ } ++ ++ /** ++ * Create a HTTP Binding configuration. ++ * ++ * @param https true if you want to use SSL ++ * (e.g. false for http://domain.lt:7070/http-bind). ++ * @param host the hostname or IP address of the connection manager ++ * (e.g. domain.lt for http://domain.lt:7070/http-bind). ++ * @param port the port of the connection manager ++ * (e.g. 7070 for http://domain.lt:7070/http-bind). ++ * @param filePath the file which is described by the URL ++ * (e.g. /http-bind for http://domain.lt:7070/http-bind). ++ * @param xmppDomain the XMPP service name ++ * (e.g. domain.lt for the user alice@domain.lt) ++ */ ++ public BOSHConfiguration(boolean https, String host, int port, String filePath, String xmppDomain) { ++ super(host, port, xmppDomain); ++ setSASLAuthenticationEnabled(true); ++ ssl = https; ++ file = (filePath != null ? filePath : "/"); ++ } ++ ++ /** ++ * Create a HTTP Binding configuration. ++ * ++ * @param https true if you want to use SSL ++ * (e.g. false for http://domain.lt:7070/http-bind). ++ * @param host the hostname or IP address of the connection manager ++ * (e.g. domain.lt for http://domain.lt:7070/http-bind). ++ * @param port the port of the connection manager ++ * (e.g. 7070 for http://domain.lt:7070/http-bind). ++ * @param filePath the file which is described by the URL ++ * (e.g. /http-bind for http://domain.lt:7070/http-bind). ++ * @param proxy the configuration of a proxy server. ++ * @param xmppDomain the XMPP service name ++ * (e.g. domain.lt for the user alice@domain.lt) ++ */ ++ public BOSHConfiguration(boolean https, String host, int port, String filePath, ProxyInfo proxy, String xmppDomain) { ++ super(host, port, xmppDomain, proxy); ++ setSASLAuthenticationEnabled(true); ++ ssl = https; ++ file = (filePath != null ? filePath : "/"); ++ } ++ ++ public boolean isProxyEnabled() { ++ return (proxy != null && proxy.getProxyType() != ProxyInfo.ProxyType.NONE); ++ } ++ ++ public ProxyInfo getProxyInfo() { ++ return proxy; ++ } ++ ++ public String getProxyAddress() { ++ return (proxy != null ? proxy.getProxyAddress() : null); ++ } ++ ++ public int getProxyPort() { ++ return (proxy != null ? proxy.getProxyPort() : 8080); ++ } ++ ++ public boolean isUsingSSL() { ++ return ssl; ++ } ++ ++ public URI getURI() throws URISyntaxException { ++ if (file.charAt(0) != '/') { ++ file = '/' + file; ++ } ++ return new URI((ssl ? "https://" : "http://") + getHost() + ":" + getPort() + file); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/BOSHConnection.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/BOSHConnection.java +new file mode 100644 +index 0000000..403b89a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/BOSHConnection.java +@@ -0,0 +1,780 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2009 Jive Software. ++ * ++ * All rights reserved. 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.io.PipedReader; ++import java.io.PipedWriter; ++import java.io.Writer; ++import java.util.concurrent.ExecutorService; ++import java.util.concurrent.Executors; ++import java.util.concurrent.ThreadFactory; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.ConnectionCreationListener; ++import org.jivesoftware.smack.ConnectionListener; ++import org.jivesoftware.smack.PacketCollector; ++import org.jivesoftware.smack.Roster; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.Presence; ++import org.jivesoftware.smack.packet.XMPPError; ++import org.jivesoftware.smack.util.StringUtils; ++ ++import com.kenai.jbosh.BOSHClient; ++import com.kenai.jbosh.BOSHClientConfig; ++import com.kenai.jbosh.BOSHClientConnEvent; ++import com.kenai.jbosh.BOSHClientConnListener; ++import com.kenai.jbosh.BOSHClientRequestListener; ++import com.kenai.jbosh.BOSHClientResponseListener; ++import com.kenai.jbosh.BOSHException; ++import com.kenai.jbosh.BOSHMessageEvent; ++import com.kenai.jbosh.BodyQName; ++import com.kenai.jbosh.ComposableBody; ++ ++/** ++ * Creates a connection to a XMPP server via HTTP binding. ++ * This is specified in the XEP-0206: XMPP Over BOSH. ++ * ++ * @see Connection ++ * @author Guenther Niess ++ */ ++public class BOSHConnection extends Connection { ++ ++ /** ++ * The XMPP Over Bosh namespace. ++ */ ++ public static final String XMPP_BOSH_NS = "urn:xmpp:xbosh"; ++ ++ /** ++ * The BOSH namespace from XEP-0124. ++ */ ++ public static final String BOSH_URI = "http://jabber.org/protocol/httpbind"; ++ ++ /** ++ * The used BOSH client from the jbosh library. ++ */ ++ private BOSHClient client; ++ ++ /** ++ * Holds the initial configuration used while creating the connection. ++ */ ++ private final BOSHConfiguration config; ++ ++ // Some flags which provides some info about the current state. ++ private boolean connected = false; ++ private boolean authenticated = false; ++ private boolean anonymous = false; ++ private boolean isFirstInitialization = true; ++ private boolean wasAuthenticated = false; ++ private boolean done = false; ++ ++ /** ++ * The Thread environment for sending packet listeners. ++ */ ++ private ExecutorService listenerExecutor; ++ ++ // The readerPipe and consumer thread are used for the debugger. ++ private PipedWriter readerPipe; ++ private Thread readerConsumer; ++ ++ /** ++ * The BOSH equivalent of the stream ID which is used for DIGEST authentication. ++ */ ++ protected String authID = null; ++ ++ /** ++ * The session ID for the BOSH session with the connection manager. ++ */ ++ protected String sessionID = null; ++ ++ /** ++ * The full JID of the authenticated user. ++ */ ++ private String user = null; ++ ++ /** ++ * The roster maybe also called buddy list holds the list of the users contacts. ++ */ ++ private Roster roster = null; ++ ++ ++ /** ++ * Create a HTTP Binding connection to a XMPP server. ++ * ++ * @param https true if you want to use SSL ++ * (e.g. false for http://domain.lt:7070/http-bind). ++ * @param host the hostname or IP address of the connection manager ++ * (e.g. domain.lt for http://domain.lt:7070/http-bind). ++ * @param port the port of the connection manager ++ * (e.g. 7070 for http://domain.lt:7070/http-bind). ++ * @param filePath the file which is described by the URL ++ * (e.g. /http-bind for http://domain.lt:7070/http-bind). ++ * @param xmppDomain the XMPP service name ++ * (e.g. domain.lt for the user alice@domain.lt) ++ */ ++ public BOSHConnection(boolean https, String host, int port, String filePath, String xmppDomain) { ++ super(new BOSHConfiguration(https, host, port, filePath, xmppDomain)); ++ this.config = (BOSHConfiguration) getConfiguration(); ++ } ++ ++ /** ++ * Create a HTTP Binding connection to a XMPP server. ++ * ++ * @param config The configuration which is used for this connection. ++ */ ++ public BOSHConnection(BOSHConfiguration config) { ++ super(config); ++ this.config = config; ++ } ++ ++ public void connect() throws XMPPException { ++ if (connected) { ++ throw new IllegalStateException("Already connected to a server."); ++ } ++ done = false; ++ try { ++ // Ensure a clean starting state ++ if (client != null) { ++ client.close(); ++ client = null; ++ } ++ saslAuthentication.init(); ++ sessionID = null; ++ authID = null; ++ ++ // Initialize BOSH client ++ BOSHClientConfig.Builder cfgBuilder = BOSHClientConfig.Builder ++ .create(config.getURI(), config.getServiceName()); ++ if (config.isProxyEnabled()) { ++ cfgBuilder.setProxy(config.getProxyAddress(), config.getProxyPort()); ++ } ++ client = BOSHClient.create(cfgBuilder.build()); ++ ++ // Create an executor to deliver incoming packets to listeners. ++ // We'll use a single thread with an unbounded queue. ++ listenerExecutor = Executors ++ .newSingleThreadExecutor(new ThreadFactory() { ++ public Thread newThread(Runnable runnable) { ++ Thread thread = new Thread(runnable, ++ "Smack Listener Processor (" ++ + connectionCounterValue + ")"); ++ thread.setDaemon(true); ++ return thread; ++ } ++ }); ++ client.addBOSHClientConnListener(new BOSHConnectionListener(this)); ++ client.addBOSHClientResponseListener(new BOSHPacketReader(this)); ++ ++ // Initialize the debugger ++ if (config.isDebuggerEnabled()) { ++ initDebugger(); ++ if (isFirstInitialization) { ++ if (debugger.getReaderListener() != null) { ++ addPacketListener(debugger.getReaderListener(), null); ++ } ++ if (debugger.getWriterListener() != null) { ++ addPacketSendingListener(debugger.getWriterListener(), null); ++ } ++ } ++ } ++ ++ // Send the session creation request ++ client.send(ComposableBody.builder() ++ .setNamespaceDefinition("xmpp", XMPP_BOSH_NS) ++ .setAttribute(BodyQName.createWithPrefix(XMPP_BOSH_NS, "version", "xmpp"), "1.0") ++ .build()); ++ } catch (Exception e) { ++ throw new XMPPException("Can't connect to " + getServiceName(), e); ++ } ++ ++ // Wait for the response from the server ++ synchronized (this) { ++ long endTime = System.currentTimeMillis() + ++ SmackConfiguration.getPacketReplyTimeout() * 6; ++ while ((!connected) && (System.currentTimeMillis() < endTime)) { ++ try { ++ wait(Math.abs(endTime - System.currentTimeMillis())); ++ } ++ catch (InterruptedException e) {} ++ } ++ } ++ ++ // If there is no feedback, throw an remote server timeout error ++ if (!connected && !done) { ++ done = true; ++ String errorMessage = "Timeout reached for the connection to " ++ + getHost() + ":" + getPort() + "."; ++ throw new XMPPException( ++ errorMessage, ++ new XMPPError(XMPPError.Condition.remote_server_timeout, errorMessage)); ++ } ++ } ++ ++ public String getConnectionID() { ++ if (!connected) { ++ return null; ++ } else if (authID != null) { ++ return authID; ++ } else { ++ return sessionID; ++ } ++ } ++ ++ public Roster getRoster() { ++ if (roster == null) { ++ return null; ++ } ++ if (!config.isRosterLoadedAtLogin()) { ++ roster.reload(); ++ } ++ // If this is the first time the user has asked for the roster after calling ++ // login, we want to wait for the server to send back the user's roster. ++ // This behavior shields API users from having to worry about the fact that ++ // roster operations are asynchronous, although they'll still have to listen ++ // for changes to the roster. Note: because of this waiting logic, internal ++ // Smack code should be wary about calling the getRoster method, and may ++ // need to access the roster object directly. ++ if (!roster.rosterInitialized) { ++ try { ++ synchronized (roster) { ++ long waitTime = SmackConfiguration.getPacketReplyTimeout(); ++ long start = System.currentTimeMillis(); ++ while (!roster.rosterInitialized) { ++ if (waitTime <= 0) { ++ break; ++ } ++ roster.wait(waitTime); ++ long now = System.currentTimeMillis(); ++ waitTime -= now - start; ++ start = now; ++ } ++ } ++ } catch (InterruptedException ie) { ++ // Ignore. ++ } ++ } ++ return roster; ++ } ++ ++ public String getUser() { ++ return user; ++ } ++ ++ public boolean isAnonymous() { ++ return anonymous; ++ } ++ ++ public boolean isAuthenticated() { ++ return authenticated; ++ } ++ ++ public boolean isConnected() { ++ return connected; ++ } ++ ++ public boolean isSecureConnection() { ++ // TODO: Implement SSL usage ++ return false; ++ } ++ ++ public boolean isUsingCompression() { ++ // TODO: Implement compression ++ return false; ++ } ++ ++ public void login(String username, String password, String resource) ++ throws XMPPException { ++ if (!isConnected()) { ++ throw new IllegalStateException("Not connected to server."); ++ } ++ if (authenticated) { ++ throw new IllegalStateException("Already logged in to server."); ++ } ++ // Do partial version of nameprep on the username. ++ username = username.toLowerCase().trim(); ++ ++ String response; ++ if (config.isSASLAuthenticationEnabled() ++ && saslAuthentication.hasNonAnonymousAuthentication()) { ++ // Authenticate using SASL ++ if (password != null) { ++ response = saslAuthentication.authenticate(username, password, resource); ++ } else { ++ response = saslAuthentication.authenticate(username, resource, config.getCallbackHandler()); ++ } ++ } else { ++ // Authenticate using Non-SASL ++ response = new NonSASLAuthentication(this).authenticate(username, password, resource); ++ } ++ ++ // Set the user. ++ if (response != null) { ++ this.user = response; ++ // Update the serviceName with the one returned by the server ++ config.setServiceName(StringUtils.parseServer(response)); ++ } else { ++ this.user = username + "@" + getServiceName(); ++ if (resource != null) { ++ this.user += "/" + resource; ++ } ++ } ++ ++ // Create the roster if it is not a reconnection. ++ if (this.roster == null) { ++ if(this.rosterStorage==null){ ++ this.roster = new Roster(this); ++ } ++ else{ ++ this.roster = new Roster(this,rosterStorage); ++ } ++ } ++ if (config.isRosterLoadedAtLogin()) { ++ this.roster.reload(); ++ } ++ ++ // Set presence to online. ++ if (config.isSendPresence()) { ++ sendPacket(new Presence(Presence.Type.available)); ++ } ++ ++ // Indicate that we're now authenticated. ++ authenticated = true; ++ anonymous = false; ++ ++ // Stores the autentication for future reconnection ++ config.setLoginInfo(username, password, resource); ++ ++ // If debugging is enabled, change the the debug window title to include ++ // the ++ // name we are now logged-in as.l ++ if (config.isDebuggerEnabled() && debugger != null) { ++ debugger.userHasLogged(user); ++ } ++ } ++ ++ public void loginAnonymously() throws XMPPException { ++ if (!isConnected()) { ++ throw new IllegalStateException("Not connected to server."); ++ } ++ if (authenticated) { ++ throw new IllegalStateException("Already logged in to server."); ++ } ++ ++ String response; ++ if (config.isSASLAuthenticationEnabled() && ++ saslAuthentication.hasAnonymousAuthentication()) { ++ response = saslAuthentication.authenticateAnonymously(); ++ } ++ else { ++ // Authenticate using Non-SASL ++ response = new NonSASLAuthentication(this).authenticateAnonymously(); ++ } ++ ++ // Set the user value. ++ this.user = response; ++ // Update the serviceName with the one returned by the server ++ config.setServiceName(StringUtils.parseServer(response)); ++ ++ // Anonymous users can't have a roster. ++ roster = null; ++ ++ // Set presence to online. ++ if (config.isSendPresence()) { ++ sendPacket(new Presence(Presence.Type.available)); ++ } ++ ++ // Indicate that we're now authenticated. ++ authenticated = true; ++ anonymous = true; ++ ++ // If debugging is enabled, change the the debug window title to include the ++ // name we are now logged-in as. ++ // If DEBUG_ENABLED was set to true AFTER the connection was created the debugger ++ // will be null ++ if (config.isDebuggerEnabled() && debugger != null) { ++ debugger.userHasLogged(user); ++ } ++ } ++ ++ public void sendPacket(Packet packet) { ++ if (!isConnected()) { ++ throw new IllegalStateException("Not connected to server."); ++ } ++ if (packet == null) { ++ throw new NullPointerException("Packet is null."); ++ } ++ if (!done) { ++ // Invoke interceptors for the new packet that is about to be sent. ++ // Interceptors ++ // may modify the content of the packet. ++ firePacketInterceptors(packet); ++ ++ try { ++ send(ComposableBody.builder().setPayloadXML(packet.toXML()) ++ .build()); ++ } catch (BOSHException e) { ++ e.printStackTrace(); ++ return; ++ } ++ ++ // Process packet writer listeners. Note that we're using the ++ // sending ++ // thread so it's expected that listeners are fast. ++ firePacketSendingListeners(packet); ++ } ++ } ++ ++ public void disconnect(Presence unavailablePresence) { ++ if (!connected) { ++ return; ++ } ++ shutdown(unavailablePresence); ++ ++ // Cleanup ++ if (roster != null) { ++ roster.cleanup(); ++ roster = null; ++ } ++ sendListeners.clear(); ++ recvListeners.clear(); ++ collectors.clear(); ++ interceptors.clear(); ++ ++ // Reset the connection flags ++ wasAuthenticated = false; ++ isFirstInitialization = true; ++ ++ // Notify connection listeners of the connection closing if done hasn't already been set. ++ for (ConnectionListener listener : getConnectionListeners()) { ++ try { ++ listener.connectionClosed(); ++ } ++ catch (Exception e) { ++ // Catch and print any exception so we can recover ++ // from a faulty listener and finish the shutdown process ++ e.printStackTrace(); ++ } ++ } ++ } ++ ++ /** ++ * Closes the connection by setting presence to unavailable and closing the ++ * HTTP client. The shutdown logic will be used during a planned disconnection or when ++ * dealing with an unexpected disconnection. Unlike {@link #disconnect()} the connection's ++ * BOSH packet reader and {@link Roster} will not be removed; thus ++ * connection's state is kept. ++ * ++ * @param unavailablePresence the presence packet to send during shutdown. ++ */ ++ protected void shutdown(Presence unavailablePresence) { ++ setWasAuthenticated(authenticated); ++ authID = null; ++ sessionID = null; ++ done = true; ++ authenticated = false; ++ connected = false; ++ isFirstInitialization = false; ++ ++ try { ++ client.disconnect(ComposableBody.builder() ++ .setNamespaceDefinition("xmpp", XMPP_BOSH_NS) ++ .setPayloadXML(unavailablePresence.toXML()) ++ .build()); ++ // Wait 150 ms for processes to clean-up, then shutdown. ++ Thread.sleep(150); ++ } ++ catch (Exception e) { ++ // Ignore. ++ } ++ ++ // Close down the readers and writers. ++ if (readerPipe != null) { ++ try { ++ readerPipe.close(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ reader = null; ++ } ++ if (reader != null) { ++ try { ++ reader.close(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ reader = null; ++ } ++ if (writer != null) { ++ try { ++ writer.close(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ writer = null; ++ } ++ ++ // Shut down the listener executor. ++ if (listenerExecutor != null) { ++ listenerExecutor.shutdown(); ++ } ++ readerConsumer = null; ++ } ++ ++ /** ++ * Sets whether the connection has already logged in the server. ++ * ++ * @param wasAuthenticated true if the connection has already been authenticated. ++ */ ++ private void setWasAuthenticated(boolean wasAuthenticated) { ++ if (!this.wasAuthenticated) { ++ this.wasAuthenticated = wasAuthenticated; ++ } ++ } ++ ++ /** ++ * Send a HTTP request to the connection manager with the provided body element. ++ * ++ * @param body the body which will be sent. ++ */ ++ protected void send(ComposableBody body) throws BOSHException { ++ if (!connected) { ++ throw new IllegalStateException("Not connected to a server!"); ++ } ++ if (body == null) { ++ throw new NullPointerException("Body mustn't be null!"); ++ } ++ if (sessionID != null) { ++ body = body.rebuild().setAttribute( ++ BodyQName.create(BOSH_URI, "sid"), sessionID).build(); ++ } ++ client.send(body); ++ } ++ ++ /** ++ * Processes a packet after it's been fully parsed by looping through the ++ * installed packet collectors and listeners and letting them examine the ++ * packet to see if they are a match with the filter. ++ * ++ * @param packet the packet to process. ++ */ ++ protected void processPacket(Packet packet) { ++ if (packet == null) { ++ return; ++ } ++ ++ // Loop through all collectors and notify the appropriate ones. ++ for (PacketCollector collector : getPacketCollectors()) { ++ collector.processPacket(packet); ++ } ++ ++ // Deliver the incoming packet to listeners. ++ listenerExecutor.submit(new ListenerNotification(packet)); ++ } ++ ++ /** ++ * Initialize the SmackDebugger which allows to log and debug XML traffic. ++ */ ++ protected void initDebugger() { ++ // TODO: Maybe we want to extend the SmackDebugger for simplification ++ // and a performance boost. ++ ++ // Initialize a empty writer which discards all data. ++ writer = new Writer() { ++ public void write(char[] cbuf, int off, int len) { /* ignore */} ++ public void close() { /* ignore */ } ++ public void flush() { /* ignore */ } ++ }; ++ ++ // Initialize a pipe for received raw data. ++ try { ++ readerPipe = new PipedWriter(); ++ reader = new PipedReader(readerPipe); ++ } ++ catch (IOException e) { ++ // Ignore ++ } ++ ++ // Call the method from the parent class which initializes the debugger. ++ super.initDebugger(); ++ ++ // Add listeners for the received and sent raw data. ++ client.addBOSHClientResponseListener(new BOSHClientResponseListener() { ++ public void responseReceived(BOSHMessageEvent event) { ++ if (event.getBody() != null) { ++ try { ++ readerPipe.write(event.getBody().toXML()); ++ readerPipe.flush(); ++ } catch (Exception e) { ++ // Ignore ++ } ++ } ++ } ++ }); ++ client.addBOSHClientRequestListener(new BOSHClientRequestListener() { ++ public void requestSent(BOSHMessageEvent event) { ++ if (event.getBody() != null) { ++ try { ++ writer.write(event.getBody().toXML()); ++ } catch (Exception e) { ++ // Ignore ++ } ++ } ++ } ++ }); ++ ++ // Create and start a thread which discards all read data. ++ readerConsumer = new Thread() { ++ private Thread thread = this; ++ private int bufferLength = 1024; ++ ++ public void run() { ++ try { ++ char[] cbuf = new char[bufferLength]; ++ while (readerConsumer == thread && !done) { ++ reader.read(cbuf, 0, bufferLength); ++ } ++ } catch (IOException e) { ++ // Ignore ++ } ++ } ++ }; ++ readerConsumer.setDaemon(true); ++ readerConsumer.start(); ++ } ++ ++ /** ++ * Sends out a notification that there was an error with the connection ++ * and closes the connection. ++ * ++ * @param e the exception that causes the connection close event. ++ */ ++ protected void notifyConnectionError(Exception e) { ++ // Closes the connection temporary. A reconnection is possible ++ shutdown(new Presence(Presence.Type.unavailable)); ++ // Print the stack trace to help catch the problem ++ e.printStackTrace(); ++ // Notify connection listeners of the error. ++ for (ConnectionListener listener : getConnectionListeners()) { ++ try { ++ listener.connectionClosedOnError(e); ++ } ++ catch (Exception e2) { ++ // Catch and print any exception so we can recover ++ // from a faulty listener ++ e2.printStackTrace(); ++ } ++ } ++ } ++ ++ ++ /** ++ * A listener class which listen for a successfully established connection ++ * and connection errors and notifies the BOSHConnection. ++ * ++ * @author Guenther Niess ++ */ ++ private class BOSHConnectionListener implements BOSHClientConnListener { ++ ++ private final BOSHConnection connection; ++ ++ public BOSHConnectionListener(BOSHConnection connection) { ++ this.connection = connection; ++ } ++ ++ /** ++ * Notify the BOSHConnection about connection state changes. ++ * Process the connection listeners and try to login if the ++ * connection was formerly authenticated and is now reconnected. ++ */ ++ public void connectionEvent(BOSHClientConnEvent connEvent) { ++ try { ++ if (connEvent.isConnected()) { ++ connected = true; ++ if (isFirstInitialization) { ++ isFirstInitialization = false; ++ for (ConnectionCreationListener listener : getConnectionCreationListeners()) { ++ listener.connectionCreated(connection); ++ } ++ } ++ else { ++ try { ++ if (wasAuthenticated) { ++ connection.login( ++ config.getUsername(), ++ config.getPassword(), ++ config.getResource()); ++ } ++ for (ConnectionListener listener : getConnectionListeners()) { ++ listener.reconnectionSuccessful(); ++ } ++ } ++ catch (XMPPException e) { ++ for (ConnectionListener listener : getConnectionListeners()) { ++ listener.reconnectionFailed(e); ++ } ++ } ++ } ++ } ++ else { ++ if (connEvent.isError()) { ++ try { ++ connEvent.getCause(); ++ } ++ catch (Exception e) { ++ notifyConnectionError(e); ++ } ++ } ++ connected = false; ++ } ++ } ++ finally { ++ synchronized (connection) { ++ connection.notifyAll(); ++ } ++ } ++ } ++ } ++ ++ /** ++ * This class notifies all listeners that a packet was received. ++ */ ++ private class ListenerNotification implements Runnable { ++ ++ private Packet packet; ++ ++ public ListenerNotification(Packet packet) { ++ this.packet = packet; ++ } ++ ++ public void run() { ++ for (ListenerWrapper listenerWrapper : recvListeners.values()) { ++ listenerWrapper.notifyListener(packet); ++ } ++ } ++ } ++ ++ @Override ++ public void setRosterStorage(RosterStorage storage) ++ throws IllegalStateException { ++ if(this.roster!=null){ ++ throw new IllegalStateException("Roster is already initialized"); ++ } ++ this.rosterStorage = storage; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/BOSHPacketReader.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/BOSHPacketReader.java +new file mode 100644 +index 0000000..c86d756 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/BOSHPacketReader.java +@@ -0,0 +1,169 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2009 Jive Software. ++ * ++ * All rights reserved. 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.StringReader; ++ ++import org.jivesoftware.smack.util.PacketParserUtils; ++import org.jivesoftware.smack.sasl.SASLMechanism.Challenge; ++import org.jivesoftware.smack.sasl.SASLMechanism.Failure; ++import org.jivesoftware.smack.sasl.SASLMechanism.Success; ++import org.xmlpull.v1.XmlPullParserFactory; ++import org.xmlpull.v1.XmlPullParser; ++ ++import com.kenai.jbosh.AbstractBody; ++import com.kenai.jbosh.BOSHClientResponseListener; ++import com.kenai.jbosh.BOSHMessageEvent; ++import com.kenai.jbosh.BodyQName; ++import com.kenai.jbosh.ComposableBody; ++ ++/** ++ * Listens for XML traffic from the BOSH connection manager and parses it into ++ * packet objects. ++ * ++ * @author Guenther Niess ++ */ ++public class BOSHPacketReader implements BOSHClientResponseListener { ++ ++ private BOSHConnection connection; ++ ++ /** ++ * Create a packet reader which listen on a BOSHConnection for received ++ * HTTP responses, parse the packets and notifies the connection. ++ * ++ * @param connection the corresponding connection for the received packets. ++ */ ++ public BOSHPacketReader(BOSHConnection connection) { ++ this.connection = connection; ++ } ++ ++ /** ++ * Parse the received packets and notify the corresponding connection. ++ * ++ * @param event the BOSH client response which includes the received packet. ++ */ ++ public void responseReceived(BOSHMessageEvent event) { ++ AbstractBody body = event.getBody(); ++ if (body != null) { ++ try { ++ if (connection.sessionID == null) { ++ connection.sessionID = body.getAttribute(BodyQName.create(BOSHConnection.BOSH_URI, "sid")); ++ } ++ if (connection.authID == null) { ++ connection.authID = body.getAttribute(BodyQName.create(BOSHConnection.BOSH_URI, "authid")); ++ } ++ final XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); ++ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, ++ true); ++ parser.setInput(new StringReader(body.toXML())); ++ int eventType = parser.getEventType(); ++ do { ++ eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("body")) { ++ // ignore the container root element ++ } else if (parser.getName().equals("message")) { ++ connection.processPacket(PacketParserUtils.parseMessage(parser)); ++ } else if (parser.getName().equals("iq")) { ++ connection.processPacket(PacketParserUtils.parseIQ(parser, connection)); ++ } else if (parser.getName().equals("presence")) { ++ connection.processPacket(PacketParserUtils.parsePresence(parser)); ++ } else if (parser.getName().equals("challenge")) { ++ // The server is challenging the SASL authentication ++ // made by the client ++ final String challengeData = parser.nextText(); ++ connection.getSASLAuthentication() ++ .challengeReceived(challengeData); ++ connection.processPacket(new Challenge( ++ challengeData)); ++ } else if (parser.getName().equals("success")) { ++ connection.send(ComposableBody.builder() ++ .setNamespaceDefinition("xmpp", BOSHConnection.XMPP_BOSH_NS) ++ .setAttribute( ++ BodyQName.createWithPrefix(BOSHConnection.XMPP_BOSH_NS, "restart", "xmpp"), ++ "true") ++ .setAttribute( ++ BodyQName.create(BOSHConnection.BOSH_URI, "to"), ++ connection.getServiceName()) ++ .build()); ++ connection.getSASLAuthentication().authenticated(); ++ connection.processPacket(new Success(parser.nextText())); ++ } else if (parser.getName().equals("features")) { ++ parseFeatures(parser); ++ } else if (parser.getName().equals("failure")) { ++ if ("urn:ietf:params:xml:ns:xmpp-sasl".equals(parser.getNamespace(null))) { ++ final Failure failure = PacketParserUtils.parseSASLFailure(parser); ++ connection.getSASLAuthentication().authenticationFailed(); ++ connection.processPacket(failure); ++ } ++ } else if (parser.getName().equals("error")) { ++ throw new XMPPException(PacketParserUtils.parseStreamError(parser)); ++ } ++ } ++ } while (eventType != XmlPullParser.END_DOCUMENT); ++ } ++ catch (Exception e) { ++ if (connection.isConnected()) { ++ connection.notifyConnectionError(e); ++ } ++ } ++ } ++ } ++ ++ /** ++ * Parse and setup the XML stream features. ++ * ++ * @param parser the XML parser, positioned at the start of a message packet. ++ * @throws Exception if an exception occurs while parsing the packet. ++ */ ++ private void parseFeatures(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("mechanisms")) { ++ // The server is reporting available SASL mechanisms. Store ++ // this information ++ // which will be used later while logging (i.e. ++ // authenticating) into ++ // the server ++ connection.getSASLAuthentication().setAvailableSASLMethods( ++ PacketParserUtils.parseMechanisms(parser)); ++ } else if (parser.getName().equals("bind")) { ++ // The server requires the client to bind a resource to the ++ // stream ++ connection.getSASLAuthentication().bindingRequired(); ++ } else if (parser.getName().equals("session")) { ++ // The server supports sessions ++ connection.getSASLAuthentication().sessionsSupported(); ++ } else if (parser.getName().equals("register")) { ++ connection.getAccountManager().setSupportsAccountCreation( ++ true); ++ } ++ } else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("features")) { ++ done = true; ++ } ++ } ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/Chat.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/Chat.java +new file mode 100644 +index 0000000..5ee5fa9 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/Chat.java +@@ -0,0 +1,190 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.packet.Message; ++ ++import java.util.Set; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.concurrent.CopyOnWriteArraySet; ++ ++/** ++ * A chat is a series of messages sent between two users. Each chat has a unique ++ * thread ID, which is used to track which messages are part of a particular ++ * conversation. Some messages are sent without a thread ID, and some clients ++ * don't send thread IDs at all. Therefore, if a message without a thread ID ++ * arrives it is routed to the most recently created Chat with the message ++ * sender. ++ * ++ * @author Matt Tucker ++ */ ++public class Chat { ++ ++ private ChatManager chatManager; ++ private String threadID; ++ private String participant; ++ private final Set listeners = new CopyOnWriteArraySet(); ++ ++ /** ++ * Creates a new chat with the specified user and thread ID. ++ * ++ * @param chatManager the chatManager the chat will use. ++ * @param participant the user to chat with. ++ * @param threadID the thread ID to use. ++ */ ++ Chat(ChatManager chatManager, String participant, String threadID) { ++ this.chatManager = chatManager; ++ this.participant = participant; ++ this.threadID = threadID; ++ } ++ ++ /** ++ * Returns the thread id associated with this chat, which corresponds to the ++ * thread field of XMPP messages. This method may return null ++ * if there is no thread ID is associated with this Chat. ++ * ++ * @return the thread ID of this chat. ++ */ ++ public String getThreadID() { ++ return threadID; ++ } ++ ++ /** ++ * Returns the name of the user the chat is with. ++ * ++ * @return the name of the user the chat is occuring with. ++ */ ++ public String getParticipant() { ++ return participant; ++ } ++ ++ /** ++ * Generate a message including setting recipient, type and body. ++ * ++ * @param text the body ++ * @return the new message ++ */ ++ public Message generateMessage(String text) { ++ Message message = new Message(participant, Message.Type.chat); ++ message.setThread(threadID); ++ message.setBody(text); ++ return message; ++ } ++ ++ /** ++ * Sends the specified text as a message to the other chat participant. ++ * This is a convenience method for: ++ * ++ *

++     *     Message message = chat.createMessage();
++     *     message.setBody(messageText);
++     *     chat.sendMessage(message);
++     * 
++ * ++ * @param text the text to send. ++ * @throws XMPPException if sending the message fails. ++ */ ++ public void sendMessage(String text) throws XMPPException { ++ chatManager.sendMessage(this, generateMessage(text)); ++ } ++ ++ /** ++ * Sends a message to the other chat participant. The thread ID, recipient, ++ * and message type of the message will automatically set to those of this chat. ++ * ++ * @param message the message to send. ++ * @throws XMPPException if an error occurs sending the message. ++ */ ++ public void sendMessage(Message message) throws XMPPException { ++ // Force the recipient, message type, and thread ID since the user elected ++ // to send the message through this chat object. ++ message.setTo(participant); ++ message.setType(Message.Type.chat); ++ message.setThread(threadID); ++ chatManager.sendMessage(this, message); ++ } ++ ++ /** ++ * Adds a packet listener that will be notified of any new messages in the ++ * chat. ++ * ++ * @param listener a packet listener. ++ */ ++ public void addMessageListener(MessageListener listener) { ++ if(listener == null) { ++ return; ++ } ++ // TODO these references should be weak. ++ listeners.add(listener); ++ } ++ ++ public void removeMessageListener(MessageListener listener) { ++ listeners.remove(listener); ++ } ++ ++ /** ++ * Returns an unmodifiable collection of all of the listeners registered with this chat. ++ * ++ * @return an unmodifiable collection of all of the listeners registered with this chat. ++ */ ++ public Collection getListeners() { ++ return Collections.unmodifiableCollection(listeners); ++ } ++ ++ /** ++ * Creates a {@link org.jivesoftware.smack.PacketCollector} which will accumulate the Messages ++ * for this chat. Always cancel PacketCollectors when finished with them as they will accumulate ++ * messages indefinitely. ++ * ++ * @return the PacketCollector which returns Messages for this chat. ++ */ ++ public PacketCollector createCollector() { ++ return chatManager.createPacketCollector(this); ++ } ++ ++ /** ++ * Delivers a message directly to this chat, which will add the message ++ * to the collector and deliver it to all listeners registered with the ++ * Chat. This is used by the Connection class to deliver messages ++ * without a thread ID. ++ * ++ * @param message the message. ++ */ ++ void deliver(Message message) { ++ // Because the collector and listeners are expecting a thread ID with ++ // a specific value, set the thread ID on the message even though it ++ // probably never had one. ++ message.setThread(threadID); ++ ++ for (MessageListener listener : listeners) { ++ listener.processMessage(this, message); ++ } ++ } ++ ++ ++ @Override ++ public boolean equals(Object obj) { ++ return obj instanceof Chat ++ && threadID.equals(((Chat)obj).getThreadID()) ++ && participant.equals(((Chat)obj).getParticipant()); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/ChatManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/ChatManager.java +new file mode 100644 +index 0000000..22dc3f9 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/ChatManager.java +@@ -0,0 +1,284 @@ ++/** ++ * $RCSfile$ ++ * $Revision: 2407 $ ++ * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.util.Collection; ++import java.util.Collections; ++import java.util.Map; ++import java.util.Set; ++import java.util.WeakHashMap; ++import java.util.concurrent.CopyOnWriteArraySet; ++ ++import org.jivesoftware.smack.filter.AndFilter; ++import org.jivesoftware.smack.filter.FromContainsFilter; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.ThreadFilter; ++import org.jivesoftware.smack.packet.Message; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.util.StringUtils; ++import org.jivesoftware.smack.util.collections.ReferenceMap; ++ ++import java.util.*; ++import java.util.concurrent.CopyOnWriteArraySet; ++ ++/** ++ * The chat manager keeps track of references to all current chats. It will not hold any references ++ * in memory on its own so it is neccesary to keep a reference to the chat object itself. To be ++ * made aware of new chats, register a listener by calling {@link #addChatListener(ChatManagerListener)}. ++ * ++ * @author Alexander Wenckus ++ */ ++public class ChatManager { ++ ++ /** ++ * Returns the next unique id. Each id made up of a short alphanumeric ++ * prefix along with a unique numeric value. ++ * ++ * @return the next id. ++ */ ++ private static synchronized String nextID() { ++ return prefix + Long.toString(id++); ++ } ++ ++ /** ++ * A prefix helps to make sure that ID's are unique across mutliple instances. ++ */ ++ private static String prefix = StringUtils.randomString(5); ++ ++ /** ++ * Keeps track of the current increment, which is appended to the prefix to ++ * forum a unique ID. ++ */ ++ private static long id = 0; ++ ++ /** ++ * Maps thread ID to chat. ++ */ ++ private Map threadChats = Collections.synchronizedMap(new ReferenceMap(ReferenceMap.HARD, ++ ReferenceMap.WEAK)); ++ ++ /** ++ * Maps jids to chats ++ */ ++ private Map jidChats = Collections.synchronizedMap(new ReferenceMap(ReferenceMap.HARD, ++ ReferenceMap.WEAK)); ++ ++ /** ++ * Maps base jids to chats ++ */ ++ private Map baseJidChats = Collections.synchronizedMap(new ReferenceMap(ReferenceMap.HARD, ++ ReferenceMap.WEAK)); ++ ++ private Set chatManagerListeners ++ = new CopyOnWriteArraySet(); ++ ++ private Map interceptors ++ = new WeakHashMap(); ++ ++ private Connection connection; ++ ++ ChatManager(Connection connection) { ++ this.connection = connection; ++ ++ PacketFilter filter = new PacketFilter() { ++ public boolean accept(Packet packet) { ++ if (!(packet instanceof Message)) { ++ return false; ++ } ++ Message.Type messageType = ((Message) packet).getType(); ++ return messageType != Message.Type.groupchat && ++ messageType != Message.Type.headline; ++ } ++ }; ++ // Add a listener for all message packets so that we can deliver errant ++ // messages to the best Chat instance available. ++ connection.addPacketListener(new PacketListener() { ++ public void processPacket(Packet packet) { ++ Message message = (Message) packet; ++ Chat chat; ++ if (message.getThread() == null) { ++ chat = getUserChat(message.getFrom()); ++ } ++ else { ++ chat = getThreadChat(message.getThread()); ++ if (chat == null) { ++ // Try to locate the chat based on the sender of the message ++ chat = getUserChat(message.getFrom()); ++ } ++ } ++ ++ if(chat == null) { ++ chat = createChat(message); ++ } ++ deliverMessage(chat, message); ++ } ++ }, filter); ++ } ++ ++ /** ++ * Creates a new chat and returns it. ++ * ++ * @param userJID the user this chat is with. ++ * @param listener the listener which will listen for new messages from this chat. ++ * @return the created chat. ++ */ ++ public Chat createChat(String userJID, MessageListener listener) { ++ String threadID; ++ do { ++ threadID = nextID(); ++ } while (threadChats.get(threadID) != null); ++ ++ return createChat(userJID, threadID, listener); ++ } ++ ++ /** ++ * Creates a new chat using the specified thread ID, then returns it. ++ * ++ * @param userJID the jid of the user this chat is with ++ * @param thread the thread of the created chat. ++ * @param listener the listener to add to the chat ++ * @return the created chat. ++ */ ++ public Chat createChat(String userJID, String thread, MessageListener listener) { ++ if(thread == null) { ++ thread = nextID(); ++ } ++ Chat chat = threadChats.get(thread); ++ if(chat != null) { ++ throw new IllegalArgumentException("ThreadID is already used"); ++ } ++ chat = createChat(userJID, thread, true); ++ chat.addMessageListener(listener); ++ return chat; ++ } ++ ++ private Chat createChat(String userJID, String threadID, boolean createdLocally) { ++ Chat chat = new Chat(this, userJID, threadID); ++ threadChats.put(threadID, chat); ++ jidChats.put(userJID, chat); ++ baseJidChats.put(StringUtils.parseBareAddress(userJID), chat); ++ ++ for(ChatManagerListener listener : chatManagerListeners) { ++ listener.chatCreated(chat, createdLocally); ++ } ++ ++ return chat; ++ } ++ ++ private Chat createChat(Message message) { ++ String threadID = message.getThread(); ++ if(threadID == null) { ++ threadID = nextID(); ++ } ++ String userJID = message.getFrom(); ++ ++ return createChat(userJID, threadID, false); ++ } ++ ++ /** ++ * Try to get a matching chat for the given user JID. Try the full ++ * JID map first, the try to match on the base JID if no match is ++ * found. ++ * ++ * @param userJID ++ * @return ++ */ ++ private Chat getUserChat(String userJID) { ++ Chat match = jidChats.get(userJID); ++ ++ if (match == null) { ++ match = baseJidChats.get(StringUtils.parseBareAddress(userJID)); ++ } ++ return match; ++ } ++ ++ public Chat getThreadChat(String thread) { ++ return threadChats.get(thread); ++ } ++ ++ /** ++ * Register a new listener with the ChatManager to recieve events related to chats. ++ * ++ * @param listener the listener. ++ */ ++ public void addChatListener(ChatManagerListener listener) { ++ chatManagerListeners.add(listener); ++ } ++ ++ /** ++ * Removes a listener, it will no longer be notified of new events related to chats. ++ * ++ * @param listener the listener that is being removed ++ */ ++ public void removeChatListener(ChatManagerListener listener) { ++ chatManagerListeners.remove(listener); ++ } ++ ++ /** ++ * Returns an unmodifiable collection of all chat listeners currently registered with this ++ * manager. ++ * ++ * @return an unmodifiable collection of all chat listeners currently registered with this ++ * manager. ++ */ ++ public Collection getChatListeners() { ++ return Collections.unmodifiableCollection(chatManagerListeners); ++ } ++ ++ private void deliverMessage(Chat chat, Message message) { ++ // Here we will run any interceptors ++ chat.deliver(message); ++ } ++ ++ void sendMessage(Chat chat, Message message) { ++ for(Map.Entry interceptor : interceptors.entrySet()) { ++ PacketFilter filter = interceptor.getValue(); ++ if(filter != null && filter.accept(message)) { ++ interceptor.getKey().interceptPacket(message); ++ } ++ } ++ // Ensure that messages being sent have a proper FROM value ++ if (message.getFrom() == null) { ++ message.setFrom(connection.getUser()); ++ } ++ connection.sendPacket(message); ++ } ++ ++ PacketCollector createPacketCollector(Chat chat) { ++ return connection.createPacketCollector(new AndFilter(new ThreadFilter(chat.getThreadID()), ++ new FromContainsFilter(chat.getParticipant()))); ++ } ++ ++ /** ++ * Adds an interceptor which intercepts any messages sent through chats. ++ * ++ * @param packetInterceptor the interceptor. ++ */ ++ public void addOutgoingMessageInterceptor(PacketInterceptor packetInterceptor) { ++ addOutgoingMessageInterceptor(packetInterceptor, null); ++ } ++ ++ public void addOutgoingMessageInterceptor(PacketInterceptor packetInterceptor, PacketFilter filter) { ++ if (packetInterceptor != null) { ++ interceptors.put(packetInterceptor, filter); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/ChatManagerListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/ChatManagerListener.java +new file mode 100644 +index 0000000..d7d5ab7 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/ChatManagerListener.java +@@ -0,0 +1,37 @@ ++/** ++ * $RCSfile$ ++ * $Revision: 2407 $ ++ * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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; ++ ++/** ++ * A listener for chat related events. ++ * ++ * @author Alexander Wenckus ++ */ ++public interface ChatManagerListener { ++ ++ /** ++ * Event fired when a new chat is created. ++ * ++ * @param chat the chat that was created. ++ * @param createdLocally true if the chat was created by the local user and false if it wasn't. ++ */ ++ void chatCreated(Chat chat, boolean createdLocally); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/Connection.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/Connection.java +new file mode 100644 +index 0000000..4357d65 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/Connection.java +@@ -0,0 +1,891 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2009 Jive Software. ++ * ++ * All rights reserved. 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.Reader; ++import java.io.Writer; ++import java.lang.reflect.Constructor; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.Map; ++import java.util.Set; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.ConcurrentLinkedQueue; ++import java.util.concurrent.CopyOnWriteArrayList; ++import java.util.concurrent.CopyOnWriteArraySet; ++import java.util.concurrent.atomic.AtomicInteger; ++ ++import org.jivesoftware.smack.debugger.SmackDebugger; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.Presence; ++ ++/** ++ * The abstract Connection class provides an interface for connections to a ++ * XMPP server and implements shared methods which are used by the ++ * different types of connections (e.g. XMPPConnection or BoshConnection). ++ * ++ * To create a connection to a XMPP server a simple usage of this API might ++ * look like the following: ++ *
++ * // Create a connection to the igniterealtime.org XMPP server.
++ * Connection con = new XMPPConnection("igniterealtime.org");
++ * // Connect to the server
++ * con.connect();
++ * // Most servers require you to login before performing other tasks.
++ * con.login("jsmith", "mypass");
++ * // Start a new conversation with John Doe and send him a message.
++ * Chat chat = connection.getChatManager().createChat("jdoe@igniterealtime.org", new MessageListener() {
++ * 

++ * public void processMessage(Chat chat, Message message) { ++ * // Print out any messages we get back to standard out. ++ * System.out.println("Received message: " + message); ++ * } ++ * }); ++ * chat.sendMessage("Howdy!"); ++ * // Disconnect from the server ++ * con.disconnect(); ++ *

++ *

++ * Connections can be reused between connections. This means that an Connection ++ * may be connected, disconnected and then connected again. Listeners of the Connection ++ * will be retained accross connections.

++ *

++ * If a connected Connection gets disconnected abruptly then it will try to reconnect ++ * again. To stop the reconnection process, use {@link #disconnect()}. Once stopped ++ * you can use {@link #connect()} to manually connect to the server. ++ * ++ * @see XMPPConnection ++ * @author Matt Tucker ++ * @author Guenther Niess ++ */ ++public abstract class Connection { ++ ++ /** ++ * Counter to uniquely identify connections that are created. ++ */ ++ private final static AtomicInteger connectionCounter = new AtomicInteger(0); ++ ++ /** ++ * A set of listeners which will be invoked if a new connection is created. ++ */ ++ private final static Set connectionEstablishedListeners = ++ new CopyOnWriteArraySet(); ++ ++ /** ++ * Value that indicates whether debugging is enabled. When enabled, a debug ++ * window will apear for each new connection that will contain the following ++ * information:

    ++ *
  • Client Traffic -- raw XML traffic generated by Smack and sent to the server. ++ *
  • Server Traffic -- raw XML traffic sent by the server to the client. ++ *
  • Interpreted Packets -- shows XML packets from the server as parsed by Smack. ++ *
++ *

++ * Debugging can be enabled by setting this field to true, or by setting the Java system ++ * property smack.debugEnabled to true. The system property can be set on the ++ * command line such as "java SomeApp -Dsmack.debugEnabled=true". ++ */ ++ public static boolean DEBUG_ENABLED = false; ++ ++ static { ++ // Use try block since we may not have permission to get a system ++ // property (for example, when an applet). ++ try { ++ DEBUG_ENABLED = Boolean.getBoolean("smack.debugEnabled"); ++ } ++ catch (Exception e) { ++ // Ignore. ++ } ++ // Ensure the SmackConfiguration class is loaded by calling a method in it. ++ SmackConfiguration.getVersion(); ++ } ++ ++ /** ++ * A collection of ConnectionListeners which listen for connection closing ++ * and reconnection events. ++ */ ++ protected final Collection connectionListeners = ++ new CopyOnWriteArrayList(); ++ ++ /** ++ * A collection of PacketCollectors which collects packets for a specified filter ++ * and perform blocking and polling operations on the result queue. ++ */ ++ protected final Collection collectors = new ConcurrentLinkedQueue(); ++ ++ /** ++ * List of PacketListeners that will be notified when a new packet was received. ++ */ ++ protected final Map recvListeners = ++ new ConcurrentHashMap(); ++ ++ /** ++ * List of PacketListeners that will be notified when a new packet was sent. ++ */ ++ protected final Map sendListeners = ++ new ConcurrentHashMap(); ++ ++ /** ++ * List of PacketInterceptors that will be notified when a new packet is about to be ++ * sent to the server. These interceptors may modify the packet before it is being ++ * actually sent to the server. ++ */ ++ protected final Map interceptors = ++ new ConcurrentHashMap(); ++ ++ /** ++ * The AccountManager allows creation and management of accounts on an XMPP server. ++ */ ++ private AccountManager accountManager = null; ++ ++ /** ++ * The ChatManager keeps track of references to all current chats. ++ */ ++ protected ChatManager chatManager = null; ++ ++ /** ++ * The SmackDebugger allows to log and debug XML traffic. ++ */ ++ protected SmackDebugger debugger = null; ++ ++ /** ++ * The Reader which is used for the {@see debugger}. ++ */ ++ protected Reader reader; ++ ++ /** ++ * The Writer which is used for the {@see debugger}. ++ */ ++ protected Writer writer; ++ ++ /** ++ * The permanent storage for the roster ++ */ ++ protected RosterStorage rosterStorage; ++ ++ ++ /** ++ * The SASLAuthentication manager that is responsible for authenticating with the server. ++ */ ++ protected SASLAuthentication saslAuthentication = new SASLAuthentication(this); ++ ++ /** ++ * A number to uniquely identify connections that are created. This is distinct from the ++ * connection ID, which is a value sent by the server once a connection is made. ++ */ ++ protected final int connectionCounterValue = connectionCounter.getAndIncrement(); ++ ++ /** ++ * Holds the initial configuration used while creating the connection. ++ */ ++ protected final ConnectionConfiguration config; ++ ++ protected String connectionID = null; ++ ++ /** ++ * Create a new Connection to a XMPP server. ++ * ++ * @param configuration The configuration which is used to establish the connection. ++ */ ++ protected Connection(ConnectionConfiguration configuration) { ++ config = configuration; ++ } ++ ++ /** ++ * Returns the configuration used to connect to the server. ++ * ++ * @return the configuration used to connect to the server. ++ */ ++ protected ConnectionConfiguration getConfiguration() { ++ return config; ++ } ++ ++ /** ++ * Returns the name of the service provided by the XMPP server for this connection. ++ * This is also called XMPP domain of the connected server. After ++ * authenticating with the server the returned value may be different. ++ * ++ * @return the name of the service provided by the XMPP server. ++ */ ++ public String getServiceName() { ++ return config.getServiceName(); ++ } ++ ++ /** ++ * Returns the host name of the server where the XMPP server is running. This would be the ++ * IP address of the server or a name that may be resolved by a DNS server. ++ * ++ * @return the host name of the server where the XMPP server is running. ++ */ ++ public String getHost() { ++ return config.getHost(); ++ } ++ ++ public String getCapsNode(){ ++ return config.getCapsNode(); ++ } ++ ++ /** ++ * Returns the port number of the XMPP server for this connection. The default port ++ * for normal connections is 5222. The default port for SSL connections is 5223. ++ * ++ * @return the port number of the XMPP server. ++ */ ++ public int getPort() { ++ return config.getPort(); ++ } ++ ++ /** ++ * Returns the full XMPP address of the user that is logged in to the connection or ++ * null if not logged in yet. An XMPP address is in the form ++ * username@server/resource. ++ * ++ * @return the full XMPP address of the user logged in. ++ */ ++ public abstract String getUser(); ++ ++ /** ++ * Returns the connection ID for this connection, which is the value set by the server ++ * when opening a XMPP stream. If the server does not set a connection ID, this value ++ * will be null. This value will be null if not connected to the server. ++ * ++ * @return the ID of this connection returned from the XMPP server or null if ++ * not connected to the server. ++ */ ++ public abstract String getConnectionID(); ++ ++ /** ++ * Returns true if currently connected to the XMPP server. ++ * ++ * @return true if connected. ++ */ ++ public abstract boolean isConnected(); ++ ++ /** ++ * Returns true if currently authenticated by successfully calling the login method. ++ * ++ * @return true if authenticated. ++ */ ++ public abstract boolean isAuthenticated(); ++ ++ /** ++ * Returns true if currently authenticated anonymously. ++ * ++ * @return true if authenticated anonymously. ++ */ ++ public abstract boolean isAnonymous(); ++ ++ /** ++ * Returns true if the connection to the server has successfully negotiated encryption. ++ * ++ * @return true if a secure connection to the server. ++ */ ++ public abstract boolean isSecureConnection(); ++ ++ /** ++ * Returns if the reconnection mechanism is allowed to be used. By default ++ * reconnection is allowed. ++ * ++ * @return true if the reconnection mechanism is allowed to be used. ++ */ ++ protected boolean isReconnectionAllowed() { ++ return config.isReconnectionAllowed(); ++ } ++ ++ /** ++ * Returns true if network traffic is being compressed. When using stream compression network ++ * traffic can be reduced up to 90%. Therefore, stream compression is ideal when using a slow ++ * speed network connection. However, the server will need to use more CPU time in order to ++ * un/compress network data so under high load the server performance might be affected. ++ * ++ * @return true if network traffic is being compressed. ++ */ ++ public abstract boolean isUsingCompression(); ++ ++ /** ++ * Establishes a connection to the XMPP server and performs an automatic login ++ * only if the previous connection state was logged (authenticated). It basically ++ * creates and maintains a connection to the server.

++ *

++ * Listeners will be preserved from a previous connection if the reconnection ++ * occurs after an abrupt termination. ++ * ++ * @throws XMPPException if an error occurs while trying to establish the connection. ++ */ ++ public abstract void connect() throws XMPPException; ++ ++ /** ++ * Logs in to the server using the strongest authentication mode supported by ++ * the server, then sets presence to available. If the server supports SASL authentication ++ * then the user will be authenticated using SASL if not Non-SASL authentication will ++ * be tried. If more than five seconds (default timeout) elapses in each step of the ++ * authentication process without a response from the server, or if an error occurs, a ++ * XMPPException will be thrown.

++ * ++ * Before logging in (i.e. authenticate) to the server the connection must be connected. ++ * ++ * It is possible to log in without sending an initial available presence by using ++ * {@link ConnectionConfiguration#setSendPresence(boolean)}. If this connection is ++ * not interested in loading its roster upon login then use ++ * {@link ConnectionConfiguration#setRosterLoadedAtLogin(boolean)}. ++ * Finally, if you want to not pass a password and instead use a more advanced mechanism ++ * while using SASL then you may be interested in using ++ * {@link ConnectionConfiguration#setCallbackHandler(javax.security.auth.callback.CallbackHandler)}. ++ * For more advanced login settings see {@link ConnectionConfiguration}. ++ * ++ * @param username the username. ++ * @param password the password or null if using a CallbackHandler. ++ * @throws XMPPException if an error occurs. ++ */ ++ public void login(String username, String password) throws XMPPException { ++ login(username, password, "Smack"); ++ } ++ ++ /** ++ * Logs in to the server using the strongest authentication mode supported by ++ * the server, then sets presence to available. If the server supports SASL authentication ++ * then the user will be authenticated using SASL if not Non-SASL authentication will ++ * be tried. If more than five seconds (default timeout) elapses in each step of the ++ * authentication process without a response from the server, or if an error occurs, a ++ * XMPPException will be thrown.

++ * ++ * Before logging in (i.e. authenticate) to the server the connection must be connected. ++ * ++ * It is possible to log in without sending an initial available presence by using ++ * {@link ConnectionConfiguration#setSendPresence(boolean)}. If this connection is ++ * not interested in loading its roster upon login then use ++ * {@link ConnectionConfiguration#setRosterLoadedAtLogin(boolean)}. ++ * Finally, if you want to not pass a password and instead use a more advanced mechanism ++ * while using SASL then you may be interested in using ++ * {@link ConnectionConfiguration#setCallbackHandler(javax.security.auth.callback.CallbackHandler)}. ++ * For more advanced login settings see {@link ConnectionConfiguration}. ++ * ++ * @param username the username. ++ * @param password the password or null if using a CallbackHandler. ++ * @param resource the resource. ++ * @throws XMPPException if an error occurs. ++ * @throws IllegalStateException if not connected to the server, or already logged in ++ * to the serrver. ++ */ ++ public abstract void login(String username, String password, String resource) throws XMPPException; ++ ++ /** ++ * Logs in to the server anonymously. Very few servers are configured to support anonymous ++ * authentication, so it's fairly likely logging in anonymously will fail. If anonymous login ++ * does succeed, your XMPP address will likely be in the form "123ABC@server/789XYZ" or ++ * "server/123ABC" (where "123ABC" and "789XYZ" is a random value generated by the server). ++ * ++ * @throws XMPPException if an error occurs or anonymous logins are not supported by the server. ++ * @throws IllegalStateException if not connected to the server, or already logged in ++ * to the serrver. ++ */ ++ public abstract void loginAnonymously() throws XMPPException; ++ ++ /** ++ * Sends the specified packet to the server. ++ * ++ * @param packet the packet to send. ++ */ ++ public abstract void sendPacket(Packet packet); ++ ++ /** ++ * Returns an account manager instance for this connection. ++ * ++ * @return an account manager for this connection. ++ */ ++ public AccountManager getAccountManager() { ++ if (accountManager == null) { ++ accountManager = new AccountManager(this); ++ } ++ return accountManager; ++ } ++ ++ /** ++ * Returns a chat manager instance for this connection. The ChatManager manages all incoming and ++ * outgoing chats on the current connection. ++ * ++ * @return a chat manager instance for this connection. ++ */ ++ public synchronized ChatManager getChatManager() { ++ if (this.chatManager == null) { ++ this.chatManager = new ChatManager(this); ++ } ++ return this.chatManager; ++ } ++ ++ /** ++ * Returns the roster for the user. ++ *

++ * This method will never return null, instead if the user has not yet logged into ++ * the server or is logged in anonymously all modifying methods of the returned roster object ++ * like {@link Roster#createEntry(String, String, String[])}, ++ * {@link Roster#removeEntry(RosterEntry)} , etc. except adding or removing ++ * {@link RosterListener}s will throw an IllegalStateException. ++ * ++ * @return the user's roster. ++ */ ++ public abstract Roster getRoster(); ++ ++ /** ++ * Set the store for the roster of this connection. If you set the roster storage ++ * of a connection you enable support for XEP-0237 (RosterVersioning) ++ * @param store the store used for roster versioning ++ * @throws IllegalStateException if you add a roster store when roster is initializied ++ */ ++ public abstract void setRosterStorage(RosterStorage storage) throws IllegalStateException; ++ ++ /** ++ * Returns the SASLAuthentication manager that is responsible for authenticating with ++ * the server. ++ * ++ * @return the SASLAuthentication manager that is responsible for authenticating with ++ * the server. ++ */ ++ public SASLAuthentication getSASLAuthentication() { ++ return saslAuthentication; ++ } ++ ++ /** ++ * Closes the connection by setting presence to unavailable then closing the connection to ++ * the XMPP server. The Connection can still be used for connecting to the server ++ * again.

++ *

++ * This method cleans up all resources used by the connection. Therefore, the roster, ++ * listeners and other stateful objects cannot be re-used by simply calling connect() ++ * on this connection again. This is unlike the behavior during unexpected disconnects ++ * (and subsequent connections). In that case, all state is preserved to allow for ++ * more seamless error recovery. ++ */ ++ public void disconnect() { ++ disconnect(new Presence(Presence.Type.unavailable)); ++ } ++ ++ /** ++ * Closes the connection. A custom unavailable presence is sent to the server, followed ++ * by closing the stream. The Connection can still be used for connecting to the server ++ * again. A custom unavilable presence is useful for communicating offline presence ++ * information such as "On vacation". Typically, just the status text of the presence ++ * packet is set with online information, but most XMPP servers will deliver the full ++ * presence packet with whatever data is set.

++ *

++ * This method cleans up all resources used by the connection. Therefore, the roster, ++ * listeners and other stateful objects cannot be re-used by simply calling connect() ++ * on this connection again. This is unlike the behavior during unexpected disconnects ++ * (and subsequent connections). In that case, all state is preserved to allow for ++ * more seamless error recovery. ++ * ++ * @param unavailablePresence the presence packet to send during shutdown. ++ */ ++ public abstract void disconnect(Presence unavailablePresence); ++ ++ /** ++ * Adds a new listener that will be notified when new Connections are created. Note ++ * that newly created connections will not be actually connected to the server. ++ * ++ * @param connectionCreationListener a listener interested on new connections. ++ */ ++ public static void addConnectionCreationListener( ++ ConnectionCreationListener connectionCreationListener) { ++ connectionEstablishedListeners.add(connectionCreationListener); ++ } ++ ++ /** ++ * Removes a listener that was interested in connection creation events. ++ * ++ * @param connectionCreationListener a listener interested on new connections. ++ */ ++ public static void removeConnectionCreationListener( ++ ConnectionCreationListener connectionCreationListener) { ++ connectionEstablishedListeners.remove(connectionCreationListener); ++ } ++ ++ /** ++ * Get the collection of listeners that are interested in connection creation events. ++ * ++ * @return a collection of listeners interested on new connections. ++ */ ++ protected static Collection getConnectionCreationListeners() { ++ return Collections.unmodifiableCollection(connectionEstablishedListeners); ++ } ++ ++ /** ++ * Adds a connection listener to this connection that will be notified when ++ * the connection closes or fails. The connection needs to already be connected ++ * or otherwise an IllegalStateException will be thrown. ++ * ++ * @param connectionListener a connection listener. ++ */ ++ public void addConnectionListener(ConnectionListener connectionListener) { ++ if (!isConnected()) { ++ throw new IllegalStateException("Not connected to server."); ++ } ++ if (connectionListener == null) { ++ return; ++ } ++ if (!connectionListeners.contains(connectionListener)) { ++ connectionListeners.add(connectionListener); ++ } ++ } ++ ++ /** ++ * Removes a connection listener from this connection. ++ * ++ * @param connectionListener a connection listener. ++ */ ++ public void removeConnectionListener(ConnectionListener connectionListener) { ++ connectionListeners.remove(connectionListener); ++ } ++ ++ /** ++ * Get the collection of listeners that are interested in connection events. ++ * ++ * @return a collection of listeners interested on connection events. ++ */ ++ protected Collection getConnectionListeners() { ++ return connectionListeners; ++ } ++ ++ /** ++ * Creates a new packet collector for this connection. A packet filter determines ++ * which packets will be accumulated by the collector. A PacketCollector is ++ * more suitable to use than a {@link PacketListener} when you need to wait for ++ * a specific result. ++ * ++ * @param packetFilter the packet filter to use. ++ * @return a new packet collector. ++ */ ++ public PacketCollector createPacketCollector(PacketFilter packetFilter) { ++ PacketCollector collector = new PacketCollector(this, packetFilter); ++ // Add the collector to the list of active collectors. ++ collectors.add(collector); ++ return collector; ++ } ++ ++ /** ++ * Remove a packet collector of this connection. ++ * ++ * @param collector a packet collectors which was created for this connection. ++ */ ++ protected void removePacketCollector(PacketCollector collector) { ++ collectors.remove(collector); ++ } ++ ++ /** ++ * Get the collection of all packet collectors for this connection. ++ * ++ * @return a collection of packet collectors for this connection. ++ */ ++ protected Collection getPacketCollectors() { ++ return collectors; ++ } ++ ++ /** ++ * Registers a packet listener with this connection. A packet filter determines ++ * which packets will be delivered to the listener. If the same packet listener ++ * is added again with a different filter, only the new filter will be used. ++ * ++ * @param packetListener the packet listener to notify of new received packets. ++ * @param packetFilter the packet filter to use. ++ */ ++ public void addPacketListener(PacketListener packetListener, PacketFilter packetFilter) { ++ if (packetListener == null) { ++ throw new NullPointerException("Packet listener is null."); ++ } ++ ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter); ++ recvListeners.put(packetListener, wrapper); ++ } ++ ++ /** ++ * Removes a packet listener for received packets from this connection. ++ * ++ * @param packetListener the packet listener to remove. ++ */ ++ public void removePacketListener(PacketListener packetListener) { ++ recvListeners.remove(packetListener); ++ } ++ ++ /** ++ * Get a map of all packet listeners for received packets of this connection. ++ * ++ * @return a map of all packet listeners for received packets. ++ */ ++ protected Map getPacketListeners() { ++ return recvListeners; ++ } ++ ++ /** ++ * Registers a packet listener with this connection. The listener will be ++ * notified of every packet that this connection sends. A packet filter determines ++ * which packets will be delivered to the listener. Note that the thread ++ * that writes packets will be used to invoke the listeners. Therefore, each ++ * packet listener should complete all operations quickly or use a different ++ * thread for processing. ++ * ++ * @param packetListener the packet listener to notify of sent packets. ++ * @param packetFilter the packet filter to use. ++ */ ++ public void addPacketSendingListener(PacketListener packetListener, PacketFilter packetFilter) { ++ if (packetListener == null) { ++ throw new NullPointerException("Packet listener is null."); ++ } ++ ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter); ++ sendListeners.put(packetListener, wrapper); ++ } ++ ++ /** ++ * Removes a packet listener for sending packets from this connection. ++ * ++ * @param packetListener the packet listener to remove. ++ */ ++ public void removePacketSendingListener(PacketListener packetListener) { ++ sendListeners.remove(packetListener); ++ } ++ ++ /** ++ * Get a map of all packet listeners for sending packets of this connection. ++ * ++ * @return a map of all packet listeners for sent packets. ++ */ ++ protected Map getPacketSendingListeners() { ++ return sendListeners; ++ } ++ ++ ++ /** ++ * Process all packet listeners for sending packets. ++ * ++ * @param packet the packet to process. ++ */ ++ protected void firePacketSendingListeners(Packet packet) { ++ // Notify the listeners of the new sent packet ++ for (ListenerWrapper listenerWrapper : sendListeners.values()) { ++ listenerWrapper.notifyListener(packet); ++ } ++ } ++ ++ /** ++ * Registers a packet interceptor with this connection. The interceptor will be ++ * invoked every time a packet is about to be sent by this connection. Interceptors ++ * may modify the packet to be sent. A packet filter determines which packets ++ * will be delivered to the interceptor. ++ * ++ * @param packetInterceptor the packet interceptor to notify of packets about to be sent. ++ * @param packetFilter the packet filter to use. ++ */ ++ public void addPacketInterceptor(PacketInterceptor packetInterceptor, ++ PacketFilter packetFilter) { ++ if (packetInterceptor == null) { ++ throw new NullPointerException("Packet interceptor is null."); ++ } ++ interceptors.put(packetInterceptor, new InterceptorWrapper(packetInterceptor, packetFilter)); ++ } ++ ++ /** ++ * Removes a packet interceptor. ++ * ++ * @param packetInterceptor the packet interceptor to remove. ++ */ ++ public void removePacketInterceptor(PacketInterceptor packetInterceptor) { ++ interceptors.remove(packetInterceptor); ++ } ++ ++ public boolean isSendPresence() { ++ return config.isSendPresence(); ++ } ++ ++ /** ++ * Get a map of all packet interceptors for sending packets of this connection. ++ * ++ * @return a map of all packet interceptors for sending packets. ++ */ ++ protected Map getPacketInterceptors() { ++ return interceptors; ++ } ++ ++ /** ++ * Process interceptors. Interceptors may modify the packet that is about to be sent. ++ * Since the thread that requested to send the packet 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 packet that is going to be sent to the server ++ */ ++ protected void firePacketInterceptors(Packet packet) { ++ if (packet != null) { ++ for (InterceptorWrapper interceptorWrapper : interceptors.values()) { ++ interceptorWrapper.notifyListener(packet); ++ } ++ } ++ } ++ ++ /** ++ * Initialize the {@link #debugger}. You can specify a customized {@link SmackDebugger} ++ * by setup the system property smack.debuggerClass to the implementation. ++ * ++ * @throws IllegalStateException if the reader or writer isn't yet initialized. ++ * @throws IllegalArgumentException if the SmackDebugger can't be loaded. ++ */ ++ protected void initDebugger() { ++ if (reader == null || writer == null) { ++ throw new NullPointerException("Reader or writer isn't initialized."); ++ } ++ // If debugging is enabled, we open a window and write out all network traffic. ++ if (config.isDebuggerEnabled()) { ++ if (debugger == null) { ++ // Detect the debugger class to use. ++ String className = null; ++ // Use try block since we may not have permission to get a system ++ // property (for example, when an applet). ++ try { ++ className = System.getProperty("smack.debuggerClass"); ++ } ++ catch (Throwable t) { ++ // Ignore. ++ } ++ Class debuggerClass = null; ++ if (className != null) { ++ try { ++ debuggerClass = Class.forName(className); ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } ++ if (debuggerClass == null) { ++ try { ++ debuggerClass = ++ Class.forName("de.measite.smack.AndroidDebugger"); ++ } ++ catch (Exception ex) { ++ try { ++ debuggerClass = ++ Class.forName("org.jivesoftware.smack.debugger.ConsoleDebugger"); ++ } ++ catch (Exception ex2) { ++ ex2.printStackTrace(); ++ } ++ } ++ } ++ // Create a new debugger instance. If an exception occurs then disable the debugging ++ // option ++ try { ++ Constructor constructor = debuggerClass ++ .getConstructor(Connection.class, Writer.class, Reader.class); ++ debugger = (SmackDebugger) constructor.newInstance(this, writer, reader); ++ reader = debugger.getReader(); ++ writer = debugger.getWriter(); ++ } ++ catch (Exception e) { ++ throw new IllegalArgumentException("Can't initialize the configured debugger!", e); ++ } ++ } ++ else { ++ // Obtain new reader and writer from the existing debugger ++ reader = debugger.newConnectionReader(reader); ++ writer = debugger.newConnectionWriter(writer); ++ } ++ } ++ ++ } ++ ++ ++ ++ /** ++ * A wrapper class to associate a packet filter with a listener. ++ */ ++ protected static class ListenerWrapper { ++ ++ private PacketListener packetListener; ++ private PacketFilter packetFilter; ++ ++ /** ++ * Create a class which associates a packet filter with a listener. ++ * ++ * @param packetListener the packet listener. ++ * @param packetFilter the associated filter or null if it listen for all packets. ++ */ ++ public ListenerWrapper(PacketListener packetListener, PacketFilter packetFilter) { ++ this.packetListener = packetListener; ++ this.packetFilter = packetFilter; ++ } ++ ++ /** ++ * Notify and process the packet listener if the filter matches the packet. ++ * ++ * @param packet the packet which was sent or received. ++ */ ++ public void notifyListener(Packet packet) { ++ if (packetFilter == null || packetFilter.accept(packet)) { ++ packetListener.processPacket(packet); ++ } ++ } ++ } ++ ++ /** ++ * A wrapper class to associate a packet filter with an interceptor. ++ */ ++ protected static class InterceptorWrapper { ++ ++ private PacketInterceptor packetInterceptor; ++ private PacketFilter packetFilter; ++ ++ /** ++ * Create a class which associates a packet filter with an interceptor. ++ * ++ * @param packetInterceptor the interceptor. ++ * @param packetFilter the associated filter or null if it intercepts all packets. ++ */ ++ public InterceptorWrapper(PacketInterceptor packetInterceptor, PacketFilter packetFilter) { ++ this.packetInterceptor = packetInterceptor; ++ this.packetFilter = packetFilter; ++ } ++ ++ public boolean equals(Object object) { ++ if (object == null) { ++ return false; ++ } ++ if (object instanceof InterceptorWrapper) { ++ return ((InterceptorWrapper) object).packetInterceptor ++ .equals(this.packetInterceptor); ++ } ++ else if (object instanceof PacketInterceptor) { ++ return object.equals(this.packetInterceptor); ++ } ++ return false; ++ } ++ ++ /** ++ * Notify and process the packet interceptor if the filter matches the packet. ++ * ++ * @param packet the packet which will be sent. ++ */ ++ public void notifyListener(Packet packet) { ++ if (packetFilter == null || packetFilter.accept(packet)) { ++ packetInterceptor.interceptPacket(packet); ++ } ++ } ++ } ++ ++ public boolean isAddFrom() { ++ // TODO Auto-generated method stub ++ return false; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/Connection.java.orig b/external/asmack/build/src/trunk/org/jivesoftware/smack/Connection.java.orig +new file mode 100644 +index 0000000..270ba33 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/Connection.java.orig +@@ -0,0 +1,891 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2009 Jive Software. ++ * ++ * All rights reserved. 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.Reader; ++import java.io.Writer; ++import java.lang.reflect.Constructor; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.Map; ++import java.util.Set; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.ConcurrentLinkedQueue; ++import java.util.concurrent.CopyOnWriteArrayList; ++import java.util.concurrent.CopyOnWriteArraySet; ++import java.util.concurrent.atomic.AtomicInteger; ++ ++import org.jivesoftware.smack.debugger.SmackDebugger; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.Presence; ++ ++/** ++ * The abstract Connection class provides an interface for connections to a ++ * XMPP server and implements shared methods which are used by the ++ * different types of connections (e.g. XMPPConnection or BoshConnection). ++ * ++ * To create a connection to a XMPP server a simple usage of this API might ++ * look like the following: ++ *

++ * // Create a connection to the igniterealtime.org XMPP server.
++ * Connection con = new XMPPConnection("igniterealtime.org");
++ * // Connect to the server
++ * con.connect();
++ * // Most servers require you to login before performing other tasks.
++ * con.login("jsmith", "mypass");
++ * // Start a new conversation with John Doe and send him a message.
++ * Chat chat = connection.getChatManager().createChat("jdoe@igniterealtime.org", new MessageListener() {
++ * 

++ * public void processMessage(Chat chat, Message message) { ++ * // Print out any messages we get back to standard out. ++ * System.out.println("Received message: " + message); ++ * } ++ * }); ++ * chat.sendMessage("Howdy!"); ++ * // Disconnect from the server ++ * con.disconnect(); ++ *

++ *

++ * Connections can be reused between connections. This means that an Connection ++ * may be connected, disconnected and then connected again. Listeners of the Connection ++ * will be retained accross connections.

++ *

++ * If a connected Connection gets disconnected abruptly then it will try to reconnect ++ * again. To stop the reconnection process, use {@link #disconnect()}. Once stopped ++ * you can use {@link #connect()} to manually connect to the server. ++ * ++ * @see XMPPConnection ++ * @author Matt Tucker ++ * @author Guenther Niess ++ */ ++public abstract class Connection { ++ ++ /** ++ * Counter to uniquely identify connections that are created. ++ */ ++ private final static AtomicInteger connectionCounter = new AtomicInteger(0); ++ ++ /** ++ * A set of listeners which will be invoked if a new connection is created. ++ */ ++ private final static Set connectionEstablishedListeners = ++ new CopyOnWriteArraySet(); ++ ++ /** ++ * Value that indicates whether debugging is enabled. When enabled, a debug ++ * window will apear for each new connection that will contain the following ++ * information:

    ++ *
  • Client Traffic -- raw XML traffic generated by Smack and sent to the server. ++ *
  • Server Traffic -- raw XML traffic sent by the server to the client. ++ *
  • Interpreted Packets -- shows XML packets from the server as parsed by Smack. ++ *
++ *

++ * Debugging can be enabled by setting this field to true, or by setting the Java system ++ * property smack.debugEnabled to true. The system property can be set on the ++ * command line such as "java SomeApp -Dsmack.debugEnabled=true". ++ */ ++ public static boolean DEBUG_ENABLED = false; ++ ++ static { ++ // Use try block since we may not have permission to get a system ++ // property (for example, when an applet). ++ try { ++ DEBUG_ENABLED = Boolean.getBoolean("smack.debugEnabled"); ++ } ++ catch (Exception e) { ++ // Ignore. ++ } ++ // Ensure the SmackConfiguration class is loaded by calling a method in it. ++ SmackConfiguration.getVersion(); ++ } ++ ++ /** ++ * A collection of ConnectionListeners which listen for connection closing ++ * and reconnection events. ++ */ ++ protected final Collection connectionListeners = ++ new CopyOnWriteArrayList(); ++ ++ /** ++ * A collection of PacketCollectors which collects packets for a specified filter ++ * and perform blocking and polling operations on the result queue. ++ */ ++ protected final Collection collectors = new ConcurrentLinkedQueue(); ++ ++ /** ++ * List of PacketListeners that will be notified when a new packet was received. ++ */ ++ protected final Map recvListeners = ++ new ConcurrentHashMap(); ++ ++ /** ++ * List of PacketListeners that will be notified when a new packet was sent. ++ */ ++ protected final Map sendListeners = ++ new ConcurrentHashMap(); ++ ++ /** ++ * List of PacketInterceptors that will be notified when a new packet is about to be ++ * sent to the server. These interceptors may modify the packet before it is being ++ * actually sent to the server. ++ */ ++ protected final Map interceptors = ++ new ConcurrentHashMap(); ++ ++ /** ++ * The AccountManager allows creation and management of accounts on an XMPP server. ++ */ ++ private AccountManager accountManager = null; ++ ++ /** ++ * The ChatManager keeps track of references to all current chats. ++ */ ++ protected ChatManager chatManager = null; ++ ++ /** ++ * The SmackDebugger allows to log and debug XML traffic. ++ */ ++ protected SmackDebugger debugger = null; ++ ++ /** ++ * The Reader which is used for the {@see debugger}. ++ */ ++ protected Reader reader; ++ ++ /** ++ * The Writer which is used for the {@see debugger}. ++ */ ++ protected Writer writer; ++ ++ /** ++ * The permanent storage for the roster ++ */ ++ protected RosterStorage rosterStorage; ++ ++ ++ /** ++ * The SASLAuthentication manager that is responsible for authenticating with the server. ++ */ ++ protected SASLAuthentication saslAuthentication = new SASLAuthentication(this); ++ ++ /** ++ * A number to uniquely identify connections that are created. This is distinct from the ++ * connection ID, which is a value sent by the server once a connection is made. ++ */ ++ protected final int connectionCounterValue = connectionCounter.getAndIncrement(); ++ ++ /** ++ * Holds the initial configuration used while creating the connection. ++ */ ++ protected final ConnectionConfiguration config; ++ ++ protected String connectionID = null; ++ ++ /** ++ * Create a new Connection to a XMPP server. ++ * ++ * @param configuration The configuration which is used to establish the connection. ++ */ ++ protected Connection(ConnectionConfiguration configuration) { ++ config = configuration; ++ } ++ ++ /** ++ * Returns the configuration used to connect to the server. ++ * ++ * @return the configuration used to connect to the server. ++ */ ++ protected ConnectionConfiguration getConfiguration() { ++ return config; ++ } ++ ++ /** ++ * Returns the name of the service provided by the XMPP server for this connection. ++ * This is also called XMPP domain of the connected server. After ++ * authenticating with the server the returned value may be different. ++ * ++ * @return the name of the service provided by the XMPP server. ++ */ ++ public String getServiceName() { ++ return config.getServiceName(); ++ } ++ ++ /** ++ * Returns the host name of the server where the XMPP server is running. This would be the ++ * IP address of the server or a name that may be resolved by a DNS server. ++ * ++ * @return the host name of the server where the XMPP server is running. ++ */ ++ public String getHost() { ++ return config.getHost(); ++ } ++ ++ public String getCapsNode(){ ++ return config.getCapsNode(); ++ } ++ ++ /** ++ * Returns the port number of the XMPP server for this connection. The default port ++ * for normal connections is 5222. The default port for SSL connections is 5223. ++ * ++ * @return the port number of the XMPP server. ++ */ ++ public int getPort() { ++ return config.getPort(); ++ } ++ ++ /** ++ * Returns the full XMPP address of the user that is logged in to the connection or ++ * null if not logged in yet. An XMPP address is in the form ++ * username@server/resource. ++ * ++ * @return the full XMPP address of the user logged in. ++ */ ++ public abstract String getUser(); ++ ++ /** ++ * Returns the connection ID for this connection, which is the value set by the server ++ * when opening a XMPP stream. If the server does not set a connection ID, this value ++ * will be null. This value will be null if not connected to the server. ++ * ++ * @return the ID of this connection returned from the XMPP server or null if ++ * not connected to the server. ++ */ ++ public abstract String getConnectionID(); ++ ++ /** ++ * Returns true if currently connected to the XMPP server. ++ * ++ * @return true if connected. ++ */ ++ public abstract boolean isConnected(); ++ ++ /** ++ * Returns true if currently authenticated by successfully calling the login method. ++ * ++ * @return true if authenticated. ++ */ ++ public abstract boolean isAuthenticated(); ++ ++ /** ++ * Returns true if currently authenticated anonymously. ++ * ++ * @return true if authenticated anonymously. ++ */ ++ public abstract boolean isAnonymous(); ++ ++ /** ++ * Returns true if the connection to the server has successfully negotiated encryption. ++ * ++ * @return true if a secure connection to the server. ++ */ ++ public abstract boolean isSecureConnection(); ++ ++ /** ++ * Returns if the reconnection mechanism is allowed to be used. By default ++ * reconnection is allowed. ++ * ++ * @return true if the reconnection mechanism is allowed to be used. ++ */ ++ protected boolean isReconnectionAllowed() { ++ return config.isReconnectionAllowed(); ++ } ++ ++ /** ++ * Returns true if network traffic is being compressed. When using stream compression network ++ * traffic can be reduced up to 90%. Therefore, stream compression is ideal when using a slow ++ * speed network connection. However, the server will need to use more CPU time in order to ++ * un/compress network data so under high load the server performance might be affected. ++ * ++ * @return true if network traffic is being compressed. ++ */ ++ public abstract boolean isUsingCompression(); ++ ++ /** ++ * Establishes a connection to the XMPP server and performs an automatic login ++ * only if the previous connection state was logged (authenticated). It basically ++ * creates and maintains a connection to the server.

++ *

++ * Listeners will be preserved from a previous connection if the reconnection ++ * occurs after an abrupt termination. ++ * ++ * @throws XMPPException if an error occurs while trying to establish the connection. ++ */ ++ public abstract void connect() throws XMPPException; ++ ++ /** ++ * Logs in to the server using the strongest authentication mode supported by ++ * the server, then sets presence to available. If the server supports SASL authentication ++ * then the user will be authenticated using SASL if not Non-SASL authentication will ++ * be tried. If more than five seconds (default timeout) elapses in each step of the ++ * authentication process without a response from the server, or if an error occurs, a ++ * XMPPException will be thrown.

++ * ++ * Before logging in (i.e. authenticate) to the server the connection must be connected. ++ * ++ * It is possible to log in without sending an initial available presence by using ++ * {@link ConnectionConfiguration#setSendPresence(boolean)}. If this connection is ++ * not interested in loading its roster upon login then use ++ * {@link ConnectionConfiguration#setRosterLoadedAtLogin(boolean)}. ++ * Finally, if you want to not pass a password and instead use a more advanced mechanism ++ * while using SASL then you may be interested in using ++ * {@link ConnectionConfiguration#setCallbackHandler(javax.security.auth.callback.CallbackHandler)}. ++ * For more advanced login settings see {@link ConnectionConfiguration}. ++ * ++ * @param username the username. ++ * @param password the password or null if using a CallbackHandler. ++ * @throws XMPPException if an error occurs. ++ */ ++ public void login(String username, String password) throws XMPPException { ++ login(username, password, "Smack"); ++ } ++ ++ /** ++ * Logs in to the server using the strongest authentication mode supported by ++ * the server, then sets presence to available. If the server supports SASL authentication ++ * then the user will be authenticated using SASL if not Non-SASL authentication will ++ * be tried. If more than five seconds (default timeout) elapses in each step of the ++ * authentication process without a response from the server, or if an error occurs, a ++ * XMPPException will be thrown.

++ * ++ * Before logging in (i.e. authenticate) to the server the connection must be connected. ++ * ++ * It is possible to log in without sending an initial available presence by using ++ * {@link ConnectionConfiguration#setSendPresence(boolean)}. If this connection is ++ * not interested in loading its roster upon login then use ++ * {@link ConnectionConfiguration#setRosterLoadedAtLogin(boolean)}. ++ * Finally, if you want to not pass a password and instead use a more advanced mechanism ++ * while using SASL then you may be interested in using ++ * {@link ConnectionConfiguration#setCallbackHandler(javax.security.auth.callback.CallbackHandler)}. ++ * For more advanced login settings see {@link ConnectionConfiguration}. ++ * ++ * @param username the username. ++ * @param password the password or null if using a CallbackHandler. ++ * @param resource the resource. ++ * @throws XMPPException if an error occurs. ++ * @throws IllegalStateException if not connected to the server, or already logged in ++ * to the serrver. ++ */ ++ public abstract void login(String username, String password, String resource) throws XMPPException; ++ ++ /** ++ * Logs in to the server anonymously. Very few servers are configured to support anonymous ++ * authentication, so it's fairly likely logging in anonymously will fail. If anonymous login ++ * does succeed, your XMPP address will likely be in the form "123ABC@server/789XYZ" or ++ * "server/123ABC" (where "123ABC" and "789XYZ" is a random value generated by the server). ++ * ++ * @throws XMPPException if an error occurs or anonymous logins are not supported by the server. ++ * @throws IllegalStateException if not connected to the server, or already logged in ++ * to the serrver. ++ */ ++ public abstract void loginAnonymously() throws XMPPException; ++ ++ /** ++ * Sends the specified packet to the server. ++ * ++ * @param packet the packet to send. ++ */ ++ public abstract void sendPacket(Packet packet); ++ ++ /** ++ * Returns an account manager instance for this connection. ++ * ++ * @return an account manager for this connection. ++ */ ++ public AccountManager getAccountManager() { ++ if (accountManager == null) { ++ accountManager = new AccountManager(this); ++ } ++ return accountManager; ++ } ++ ++ /** ++ * Returns a chat manager instance for this connection. The ChatManager manages all incoming and ++ * outgoing chats on the current connection. ++ * ++ * @return a chat manager instance for this connection. ++ */ ++ public synchronized ChatManager getChatManager() { ++ if (this.chatManager == null) { ++ this.chatManager = new ChatManager(this); ++ } ++ return this.chatManager; ++ } ++ ++ /** ++ * Returns the roster for the user. ++ *

++ * This method will never return null, instead if the user has not yet logged into ++ * the server or is logged in anonymously all modifying methods of the returned roster object ++ * like {@link Roster#createEntry(String, String, String[])}, ++ * {@link Roster#removeEntry(RosterEntry)} , etc. except adding or removing ++ * {@link RosterListener}s will throw an IllegalStateException. ++ * ++ * @return the user's roster. ++ */ ++ public abstract Roster getRoster(); ++ ++ /** ++ * Set the store for the roster of this connection. If you set the roster storage ++ * of a connection you enable support for XEP-0237 (RosterVersioning) ++ * @param store the store used for roster versioning ++ * @throws IllegalStateException if you add a roster store when roster is initializied ++ */ ++ public abstract void setRosterStorage(RosterStorage storage) throws IllegalStateException; ++ ++ /** ++ * Returns the SASLAuthentication manager that is responsible for authenticating with ++ * the server. ++ * ++ * @return the SASLAuthentication manager that is responsible for authenticating with ++ * the server. ++ */ ++ public SASLAuthentication getSASLAuthentication() { ++ return saslAuthentication; ++ } ++ ++ /** ++ * Closes the connection by setting presence to unavailable then closing the connection to ++ * the XMPP server. The Connection can still be used for connecting to the server ++ * again.

++ *

++ * This method cleans up all resources used by the connection. Therefore, the roster, ++ * listeners and other stateful objects cannot be re-used by simply calling connect() ++ * on this connection again. This is unlike the behavior during unexpected disconnects ++ * (and subsequent connections). In that case, all state is preserved to allow for ++ * more seamless error recovery. ++ */ ++ public void disconnect() { ++ disconnect(new Presence(Presence.Type.unavailable)); ++ } ++ ++ /** ++ * Closes the connection. A custom unavailable presence is sent to the server, followed ++ * by closing the stream. The Connection can still be used for connecting to the server ++ * again. A custom unavilable presence is useful for communicating offline presence ++ * information such as "On vacation". Typically, just the status text of the presence ++ * packet is set with online information, but most XMPP servers will deliver the full ++ * presence packet with whatever data is set.

++ *

++ * This method cleans up all resources used by the connection. Therefore, the roster, ++ * listeners and other stateful objects cannot be re-used by simply calling connect() ++ * on this connection again. This is unlike the behavior during unexpected disconnects ++ * (and subsequent connections). In that case, all state is preserved to allow for ++ * more seamless error recovery. ++ * ++ * @param unavailablePresence the presence packet to send during shutdown. ++ */ ++ public abstract void disconnect(Presence unavailablePresence); ++ ++ /** ++ * Adds a new listener that will be notified when new Connections are created. Note ++ * that newly created connections will not be actually connected to the server. ++ * ++ * @param connectionCreationListener a listener interested on new connections. ++ */ ++ public static void addConnectionCreationListener( ++ ConnectionCreationListener connectionCreationListener) { ++ connectionEstablishedListeners.add(connectionCreationListener); ++ } ++ ++ /** ++ * Removes a listener that was interested in connection creation events. ++ * ++ * @param connectionCreationListener a listener interested on new connections. ++ */ ++ public static void removeConnectionCreationListener( ++ ConnectionCreationListener connectionCreationListener) { ++ connectionEstablishedListeners.remove(connectionCreationListener); ++ } ++ ++ /** ++ * Get the collection of listeners that are interested in connection creation events. ++ * ++ * @return a collection of listeners interested on new connections. ++ */ ++ protected static Collection getConnectionCreationListeners() { ++ return Collections.unmodifiableCollection(connectionEstablishedListeners); ++ } ++ ++ /** ++ * Adds a connection listener to this connection that will be notified when ++ * the connection closes or fails. The connection needs to already be connected ++ * or otherwise an IllegalStateException will be thrown. ++ * ++ * @param connectionListener a connection listener. ++ */ ++ public void addConnectionListener(ConnectionListener connectionListener) { ++ if (!isConnected()) { ++ throw new IllegalStateException("Not connected to server."); ++ } ++ if (connectionListener == null) { ++ return; ++ } ++ if (!connectionListeners.contains(connectionListener)) { ++ connectionListeners.add(connectionListener); ++ } ++ } ++ ++ /** ++ * Removes a connection listener from this connection. ++ * ++ * @param connectionListener a connection listener. ++ */ ++ public void removeConnectionListener(ConnectionListener connectionListener) { ++ connectionListeners.remove(connectionListener); ++ } ++ ++ /** ++ * Get the collection of listeners that are interested in connection events. ++ * ++ * @return a collection of listeners interested on connection events. ++ */ ++ protected Collection getConnectionListeners() { ++ return connectionListeners; ++ } ++ ++ /** ++ * Creates a new packet collector for this connection. A packet filter determines ++ * which packets will be accumulated by the collector. A PacketCollector is ++ * more suitable to use than a {@link PacketListener} when you need to wait for ++ * a specific result. ++ * ++ * @param packetFilter the packet filter to use. ++ * @return a new packet collector. ++ */ ++ public PacketCollector createPacketCollector(PacketFilter packetFilter) { ++ PacketCollector collector = new PacketCollector(this, packetFilter); ++ // Add the collector to the list of active collectors. ++ collectors.add(collector); ++ return collector; ++ } ++ ++ /** ++ * Remove a packet collector of this connection. ++ * ++ * @param collector a packet collectors which was created for this connection. ++ */ ++ protected void removePacketCollector(PacketCollector collector) { ++ collectors.remove(collector); ++ } ++ ++ /** ++ * Get the collection of all packet collectors for this connection. ++ * ++ * @return a collection of packet collectors for this connection. ++ */ ++ protected Collection getPacketCollectors() { ++ return collectors; ++ } ++ ++ /** ++ * Registers a packet listener with this connection. A packet filter determines ++ * which packets will be delivered to the listener. If the same packet listener ++ * is added again with a different filter, only the new filter will be used. ++ * ++ * @param packetListener the packet listener to notify of new received packets. ++ * @param packetFilter the packet filter to use. ++ */ ++ public void addPacketListener(PacketListener packetListener, PacketFilter packetFilter) { ++ if (packetListener == null) { ++ throw new NullPointerException("Packet listener is null."); ++ } ++ ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter); ++ recvListeners.put(packetListener, wrapper); ++ } ++ ++ /** ++ * Removes a packet listener for received packets from this connection. ++ * ++ * @param packetListener the packet listener to remove. ++ */ ++ public void removePacketListener(PacketListener packetListener) { ++ recvListeners.remove(packetListener); ++ } ++ ++ /** ++ * Get a map of all packet listeners for received packets of this connection. ++ * ++ * @return a map of all packet listeners for received packets. ++ */ ++ protected Map getPacketListeners() { ++ return recvListeners; ++ } ++ ++ /** ++ * Registers a packet listener with this connection. The listener will be ++ * notified of every packet that this connection sends. A packet filter determines ++ * which packets will be delivered to the listener. Note that the thread ++ * that writes packets will be used to invoke the listeners. Therefore, each ++ * packet listener should complete all operations quickly or use a different ++ * thread for processing. ++ * ++ * @param packetListener the packet listener to notify of sent packets. ++ * @param packetFilter the packet filter to use. ++ */ ++ public void addPacketSendingListener(PacketListener packetListener, PacketFilter packetFilter) { ++ if (packetListener == null) { ++ throw new NullPointerException("Packet listener is null."); ++ } ++ ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter); ++ sendListeners.put(packetListener, wrapper); ++ } ++ ++ /** ++ * Removes a packet listener for sending packets from this connection. ++ * ++ * @param packetListener the packet listener to remove. ++ */ ++ public void removePacketSendingListener(PacketListener packetListener) { ++ sendListeners.remove(packetListener); ++ } ++ ++ /** ++ * Get a map of all packet listeners for sending packets of this connection. ++ * ++ * @return a map of all packet listeners for sent packets. ++ */ ++ protected Map getPacketSendingListeners() { ++ return sendListeners; ++ } ++ ++ ++ /** ++ * Process all packet listeners for sending packets. ++ * ++ * @param packet the packet to process. ++ */ ++ protected void firePacketSendingListeners(Packet packet) { ++ // Notify the listeners of the new sent packet ++ for (ListenerWrapper listenerWrapper : sendListeners.values()) { ++ listenerWrapper.notifyListener(packet); ++ } ++ } ++ ++ /** ++ * Registers a packet interceptor with this connection. The interceptor will be ++ * invoked every time a packet is about to be sent by this connection. Interceptors ++ * may modify the packet to be sent. A packet filter determines which packets ++ * will be delivered to the interceptor. ++ * ++ * @param packetInterceptor the packet interceptor to notify of packets about to be sent. ++ * @param packetFilter the packet filter to use. ++ */ ++ public void addPacketInterceptor(PacketInterceptor packetInterceptor, ++ PacketFilter packetFilter) { ++ if (packetInterceptor == null) { ++ throw new NullPointerException("Packet interceptor is null."); ++ } ++ interceptors.put(packetInterceptor, new InterceptorWrapper(packetInterceptor, packetFilter)); ++ } ++ ++ /** ++ * Removes a packet interceptor. ++ * ++ * @param packetInterceptor the packet interceptor to remove. ++ */ ++ public void removePacketInterceptor(PacketInterceptor packetInterceptor) { ++ interceptors.remove(packetInterceptor); ++ } ++ ++ public boolean isSendPresence() { ++ return config.isSendPresence(); ++ } ++ ++ /** ++ * Get a map of all packet interceptors for sending packets of this connection. ++ * ++ * @return a map of all packet interceptors for sending packets. ++ */ ++ protected Map getPacketInterceptors() { ++ return interceptors; ++ } ++ ++ /** ++ * Process interceptors. Interceptors may modify the packet that is about to be sent. ++ * Since the thread that requested to send the packet 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 packet that is going to be sent to the server ++ */ ++ protected void firePacketInterceptors(Packet packet) { ++ if (packet != null) { ++ for (InterceptorWrapper interceptorWrapper : interceptors.values()) { ++ interceptorWrapper.notifyListener(packet); ++ } ++ } ++ } ++ ++ /** ++ * Initialize the {@link #debugger}. You can specify a customized {@link SmackDebugger} ++ * by setup the system property smack.debuggerClass to the implementation. ++ * ++ * @throws IllegalStateException if the reader or writer isn't yet initialized. ++ * @throws IllegalArgumentException if the SmackDebugger can't be loaded. ++ */ ++ protected void initDebugger() { ++ if (reader == null || writer == null) { ++ throw new NullPointerException("Reader or writer isn't initialized."); ++ } ++ // If debugging is enabled, we open a window and write out all network traffic. ++ if (config.isDebuggerEnabled()) { ++ if (debugger == null) { ++ // Detect the debugger class to use. ++ String className = null; ++ // Use try block since we may not have permission to get a system ++ // property (for example, when an applet). ++ try { ++ className = System.getProperty("smack.debuggerClass"); ++ } ++ catch (Throwable t) { ++ // Ignore. ++ } ++ Class debuggerClass = null; ++ if (className != null) { ++ try { ++ debuggerClass = Class.forName(className); ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } ++ if (debuggerClass == null) { ++ try { ++ debuggerClass = ++ Class.forName("org.jivesoftware.smackx.debugger.EnhancedDebugger"); ++ } ++ catch (Exception ex) { ++ try { ++ debuggerClass = ++ Class.forName("org.jivesoftware.smack.debugger.LiteDebugger"); ++ } ++ catch (Exception ex2) { ++ ex2.printStackTrace(); ++ } ++ } ++ } ++ // Create a new debugger instance. If an exception occurs then disable the debugging ++ // option ++ try { ++ Constructor constructor = debuggerClass ++ .getConstructor(Connection.class, Writer.class, Reader.class); ++ debugger = (SmackDebugger) constructor.newInstance(this, writer, reader); ++ reader = debugger.getReader(); ++ writer = debugger.getWriter(); ++ } ++ catch (Exception e) { ++ throw new IllegalArgumentException("Can't initialize the configured debugger!", e); ++ } ++ } ++ else { ++ // Obtain new reader and writer from the existing debugger ++ reader = debugger.newConnectionReader(reader); ++ writer = debugger.newConnectionWriter(writer); ++ } ++ } ++ ++ } ++ ++ ++ ++ /** ++ * A wrapper class to associate a packet filter with a listener. ++ */ ++ protected static class ListenerWrapper { ++ ++ private PacketListener packetListener; ++ private PacketFilter packetFilter; ++ ++ /** ++ * Create a class which associates a packet filter with a listener. ++ * ++ * @param packetListener the packet listener. ++ * @param packetFilter the associated filter or null if it listen for all packets. ++ */ ++ public ListenerWrapper(PacketListener packetListener, PacketFilter packetFilter) { ++ this.packetListener = packetListener; ++ this.packetFilter = packetFilter; ++ } ++ ++ /** ++ * Notify and process the packet listener if the filter matches the packet. ++ * ++ * @param packet the packet which was sent or received. ++ */ ++ public void notifyListener(Packet packet) { ++ if (packetFilter == null || packetFilter.accept(packet)) { ++ packetListener.processPacket(packet); ++ } ++ } ++ } ++ ++ /** ++ * A wrapper class to associate a packet filter with an interceptor. ++ */ ++ protected static class InterceptorWrapper { ++ ++ private PacketInterceptor packetInterceptor; ++ private PacketFilter packetFilter; ++ ++ /** ++ * Create a class which associates a packet filter with an interceptor. ++ * ++ * @param packetInterceptor the interceptor. ++ * @param packetFilter the associated filter or null if it intercepts all packets. ++ */ ++ public InterceptorWrapper(PacketInterceptor packetInterceptor, PacketFilter packetFilter) { ++ this.packetInterceptor = packetInterceptor; ++ this.packetFilter = packetFilter; ++ } ++ ++ public boolean equals(Object object) { ++ if (object == null) { ++ return false; ++ } ++ if (object instanceof InterceptorWrapper) { ++ return ((InterceptorWrapper) object).packetInterceptor ++ .equals(this.packetInterceptor); ++ } ++ else if (object instanceof PacketInterceptor) { ++ return object.equals(this.packetInterceptor); ++ } ++ return false; ++ } ++ ++ /** ++ * Notify and process the packet interceptor if the filter matches the packet. ++ * ++ * @param packet the packet which will be sent. ++ */ ++ public void notifyListener(Packet packet) { ++ if (packetFilter == null || packetFilter.accept(packet)) { ++ packetInterceptor.interceptPacket(packet); ++ } ++ } ++ } ++ ++ public boolean isAddFrom() { ++ // TODO Auto-generated method stub ++ return false; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/ConnectionConfiguration.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/ConnectionConfiguration.java +new file mode 100644 +index 0000000..2d617fd +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/ConnectionConfiguration.java +@@ -0,0 +1,788 @@ ++/** ++ * $RCSfile$ ++ * $Revision: 3306 $ ++ * $Date: 2006-01-16 14:34:56 -0300 (Mon, 16 Jan 2006) $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.proxy.ProxyInfo; ++import org.jivesoftware.smack.util.DNSUtil; ++ ++import javax.net.SocketFactory; ++import javax.net.ssl.SSLContext; ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++import java.io.File; ++ ++/** ++ * Configuration to use while establishing the connection to the server. It is possible to ++ * configure the path to the trustore file that keeps the trusted CA root certificates and ++ * enable or disable all or some of the checkings done while verifying server certificates.

++ * ++ * It is also possible to configure if TLS, SASL, and compression are used or not. ++ * ++ * @author Gaston Dombiak ++ */ ++public class ConnectionConfiguration implements Cloneable { ++ ++ /** ++ * Hostname of the XMPP server. Usually servers use the same service name as the name ++ * of the server. However, there are some servers like google where host would be ++ * talk.google.com and the serviceName would be gmail.com. ++ */ ++ private String serviceName; ++ ++ private String host; ++ private int port; ++ ++ private String truststorePath; ++ private String truststoreType; ++ private String truststorePassword; ++ private String keystorePath; ++ private String keystoreType; ++ private String pkcs11Library; ++ private boolean verifyChainEnabled = false; ++ private boolean verifyRootCAEnabled = false; ++ private boolean selfSignedCertificateEnabled = false; ++ private boolean expiredCertificatesCheckEnabled = false; ++ private boolean notMatchingDomainCheckEnabled = false; ++ private boolean isRosterVersioningAvailable = false; ++ private boolean enableEntityCaps = true; ++ private String capsNode = null; ++ private SSLContext customSSLContext; ++ ++ private boolean compressionEnabled = false; ++ ++ private boolean saslAuthenticationEnabled = true; ++ /** ++ * Used to get information from the user ++ */ ++ private CallbackHandler callbackHandler; ++ ++ private boolean debuggerEnabled = Connection.DEBUG_ENABLED; ++ ++ // Flag that indicates if a reconnection should be attempted when abruptly disconnected ++ private boolean reconnectionAllowed = true; ++ ++ // Holds the socket factory that is used to generate the socket in the connection ++ private SocketFactory socketFactory; ++ ++ // Holds the authentication information for future reconnections ++ private String username; ++ private String password; ++ private String resource; ++ private boolean sendPresence = true; ++ private boolean rosterLoadedAtLogin = true; ++ private SecurityMode securityMode = SecurityMode.enabled; ++ ++ // Holds the proxy information (such as proxyhost, proxyport, username, password etc) ++ protected ProxyInfo proxy; ++ ++ /** ++ * Creates a new ConnectionConfiguration for the specified service name. ++ * A DNS SRV lookup will be performed to find out the actual host address ++ * and port to use for the connection. ++ * ++ * @param serviceName the name of the service provided by an XMPP server. ++ */ ++ public ConnectionConfiguration(String serviceName) { ++ // Perform DNS lookup to get host and port to use ++ DNSUtil.HostAddress address = DNSUtil.resolveXMPPDomain(serviceName); ++ init(address.getHost(), address.getPort(), serviceName, ++ ProxyInfo.forDefaultProxy()); ++ } ++ ++ /** ++ * ++ */ ++ protected ConnectionConfiguration() { ++ /* Does nothing */ ++ } ++ ++ /** ++ * Creates a new ConnectionConfiguration for the specified service name ++ * with specified proxy. ++ * A DNS SRV lookup will be performed to find out the actual host address ++ * and port to use for the connection. ++ * ++ * @param serviceName the name of the service provided by an XMPP server. ++ * @param proxy the proxy through which XMPP is to be connected ++ */ ++ public ConnectionConfiguration(String serviceName,ProxyInfo proxy) { ++ // Perform DNS lookup to get host and port to use ++ DNSUtil.HostAddress address = DNSUtil.resolveXMPPDomain(serviceName); ++ init(address.getHost(), address.getPort(), serviceName, proxy); ++ } ++ ++ /** ++ * Creates a new ConnectionConfiguration using the specified host, port and ++ * service name. This is useful for manually overriding the DNS SRV lookup ++ * process that's used with the {@link #ConnectionConfiguration(String)} ++ * constructor. For example, say that an XMPP server is running at localhost ++ * in an internal network on port 5222 but is configured to think that it's ++ * "example.com" for testing purposes. This constructor is necessary to connect ++ * to the server in that case since a DNS SRV lookup for example.com would not ++ * point to the local testing server. ++ * ++ * @param host the host where the XMPP server is running. ++ * @param port the port where the XMPP is listening. ++ * @param serviceName the name of the service provided by an XMPP server. ++ */ ++ public ConnectionConfiguration(String host, int port, String serviceName) { ++ init(host, port, serviceName, ProxyInfo.forDefaultProxy()); ++ } ++ ++ /** ++ * Creates a new ConnectionConfiguration using the specified host, port and ++ * service name. This is useful for manually overriding the DNS SRV lookup ++ * process that's used with the {@link #ConnectionConfiguration(String)} ++ * constructor. For example, say that an XMPP server is running at localhost ++ * in an internal network on port 5222 but is configured to think that it's ++ * "example.com" for testing purposes. This constructor is necessary to connect ++ * to the server in that case since a DNS SRV lookup for example.com would not ++ * point to the local testing server. ++ * ++ * @param host the host where the XMPP server is running. ++ * @param port the port where the XMPP is listening. ++ * @param serviceName the name of the service provided by an XMPP server. ++ * @param proxy the proxy through which XMPP is to be connected ++ */ ++ public ConnectionConfiguration(String host, int port, String serviceName, ProxyInfo proxy) { ++ init(host, port, serviceName, proxy); ++ } ++ ++ /** ++ * Creates a new ConnectionConfiguration for a connection that will connect ++ * to the desired host and port. ++ * ++ * @param host the host where the XMPP server is running. ++ * @param port the port where the XMPP is listening. ++ */ ++ public ConnectionConfiguration(String host, int port) { ++ init(host, port, host, ProxyInfo.forDefaultProxy()); ++ } ++ ++ /** ++ * Creates a new ConnectionConfiguration for a connection that will connect ++ * to the desired host and port with desired proxy. ++ * ++ * @param host the host where the XMPP server is running. ++ * @param port the port where the XMPP is listening. ++ * @param proxy the proxy through which XMPP is to be connected ++ */ ++ public ConnectionConfiguration(String host, int port, ProxyInfo proxy) { ++ init(host, port, host, proxy); ++ } ++ ++ protected void init(String host, int port, String serviceName, ProxyInfo proxy) { ++ this.host = host; ++ this.port = port; ++ this.serviceName = serviceName; ++ this.proxy = proxy; ++ ++ // Build the default path to the cacert truststore file. By default we are ++ // going to use the file located in $JREHOME/lib/security/cacerts. ++ String javaHome = System.getProperty("java.home"); ++ StringBuilder buffer = new StringBuilder(); ++ buffer.append(javaHome).append(File.separator).append("lib"); ++ buffer.append(File.separator).append("security"); ++ buffer.append(File.separator).append("cacerts"); ++ truststorePath = buffer.toString(); ++ // Set the default store type ++ truststoreType = "jks"; ++ // Set the default password of the cacert file that is "changeit" ++ truststorePassword = "changeit"; ++ keystorePath = System.getProperty("javax.net.ssl.keyStore"); ++ keystoreType = "jks"; ++ pkcs11Library = "pkcs11.config"; ++ ++ //Setting the SocketFactory according to proxy supplied ++ socketFactory = proxy.getSocketFactory(); ++ } ++ ++ /** ++ * Sets the server name, also known as XMPP domain of the target server. ++ * ++ * @param serviceName the XMPP domain of the target server. ++ */ ++ public void setServiceName(String serviceName) { ++ this.serviceName = serviceName; ++ } ++ ++ /** ++ * Returns the server name of the target server. ++ * ++ * @return the server name of the target server. ++ */ ++ public String getServiceName() { ++ return serviceName; ++ } ++ ++ /** ++ * Returns the host to use when establishing the connection. The host and port to use ++ * might have been resolved by a DNS lookup as specified by the XMPP spec (and therefore ++ * may not match the {@link #getServiceName service name}. ++ * ++ * @return the host to use when establishing the connection. ++ */ ++ public String getHost() { ++ return host; ++ } ++ ++ /** ++ * Returns the port to use when establishing the connection. The host and port to use ++ * might have been resolved by a DNS lookup as specified by the XMPP spec. ++ * ++ * @return the port to use when establishing the connection. ++ */ ++ public int getPort() { ++ return port; ++ } ++ ++ /** ++ * Returns the TLS security mode used when making the connection. By default, ++ * the mode is {@link SecurityMode#enabled}. ++ * ++ * @return the security mode. ++ */ ++ public SecurityMode getSecurityMode() { ++ return securityMode; ++ } ++ ++ /** ++ * Sets the TLS security mode used when making the connection. By default, ++ * the mode is {@link SecurityMode#enabled}. ++ * ++ * @param securityMode the security mode. ++ */ ++ public void setSecurityMode(SecurityMode securityMode) { ++ this.securityMode = securityMode; ++ } ++ ++ /** ++ * Retuns the path to the trust store file. The trust store file contains the root ++ * certificates of several well known CAs. By default, will attempt to use the ++ * the file located in $JREHOME/lib/security/cacerts. ++ * ++ * @return the path to the truststore file. ++ */ ++ public String getTruststorePath() { ++ return truststorePath; ++ } ++ ++ /** ++ * Sets the path to the trust store file. The truststore file contains the root ++ * certificates of several well?known CAs. By default Smack is going to use ++ * the file located in $JREHOME/lib/security/cacerts. ++ * ++ * @param truststorePath the path to the truststore file. ++ */ ++ public void setTruststorePath(String truststorePath) { ++ this.truststorePath = truststorePath; ++ } ++ ++ /** ++ * Returns the trust store type, or null if it's not set. ++ * ++ * @return the trust store type. ++ */ ++ public String getTruststoreType() { ++ return truststoreType; ++ } ++ ++ /** ++ * Sets the trust store type. ++ * ++ * @param truststoreType the trust store type. ++ */ ++ public void setTruststoreType(String truststoreType) { ++ this.truststoreType = truststoreType; ++ } ++ ++ /** ++ * Returns the password to use to access the trust store file. It is assumed that all ++ * certificates share the same password in the trust store. ++ * ++ * @return the password to use to access the truststore file. ++ */ ++ public String getTruststorePassword() { ++ return truststorePassword; ++ } ++ ++ /** ++ * Sets the password to use to access the trust store file. It is assumed that all ++ * certificates share the same password in the trust store. ++ * ++ * @param truststorePassword the password to use to access the truststore file. ++ */ ++ public void setTruststorePassword(String truststorePassword) { ++ this.truststorePassword = truststorePassword; ++ } ++ ++ /** ++ * Retuns the path to the keystore file. The key store file contains the ++ * certificates that may be used to authenticate the client to the server, ++ * in the event the server requests or requires it. ++ * ++ * @return the path to the keystore file. ++ */ ++ public String getKeystorePath() { ++ return keystorePath; ++ } ++ ++ /** ++ * Sets the path to the keystore file. The key store file contains the ++ * certificates that may be used to authenticate the client to the server, ++ * in the event the server requests or requires it. ++ * ++ * @param keystorePath the path to the keystore file. ++ */ ++ public void setKeystorePath(String keystorePath) { ++ this.keystorePath = keystorePath; ++ } ++ ++ /** ++ * Returns the keystore type, or null if it's not set. ++ * ++ * @return the keystore type. ++ */ ++ public String getKeystoreType() { ++ return keystoreType; ++ } ++ ++ /** ++ * Sets the keystore type. ++ * ++ * @param keystoreType the keystore type. ++ */ ++ public void setKeystoreType(String keystoreType) { ++ this.keystoreType = keystoreType; ++ } ++ ++ ++ /** ++ * Returns the PKCS11 library file location, needed when the ++ * Keystore type is PKCS11. ++ * ++ * @return the path to the PKCS11 library file ++ */ ++ public String getPKCS11Library() { ++ return pkcs11Library; ++ } ++ ++ /** ++ * Sets the PKCS11 library file location, needed when the ++ * Keystore type is PKCS11 ++ * ++ * @param pkcs11Library the path to the PKCS11 library file ++ */ ++ public void setPKCS11Library(String pkcs11Library) { ++ this.pkcs11Library = pkcs11Library; ++ } ++ ++ /** ++ * Returns true if the whole chain of certificates presented by the server are going to ++ * be checked. By default the certificate chain is not verified. ++ * ++ * @return true if the whole chaing of certificates presented by the server are going to ++ * be checked. ++ */ ++ public boolean isVerifyChainEnabled() { ++ return verifyChainEnabled; ++ } ++ ++ /** ++ * Sets if the whole chain of certificates presented by the server are going to ++ * be checked. By default the certificate chain is not verified. ++ * ++ * @param verifyChainEnabled if the whole chaing of certificates presented by the server ++ * are going to be checked. ++ */ ++ public void setVerifyChainEnabled(boolean verifyChainEnabled) { ++ this.verifyChainEnabled = verifyChainEnabled; ++ } ++ ++ /** ++ * Returns true if root CA checking is going to be done. By default checking is disabled. ++ * ++ * @return true if root CA checking is going to be done. ++ */ ++ public boolean isVerifyRootCAEnabled() { ++ return verifyRootCAEnabled; ++ } ++ ++ /** ++ * Sets if root CA checking is going to be done. By default checking is disabled. ++ * ++ * @param verifyRootCAEnabled if root CA checking is going to be done. ++ */ ++ public void setVerifyRootCAEnabled(boolean verifyRootCAEnabled) { ++ this.verifyRootCAEnabled = verifyRootCAEnabled; ++ } ++ ++ /** ++ * Returns true if self-signed certificates are going to be accepted. By default ++ * this option is disabled. ++ * ++ * @return true if self-signed certificates are going to be accepted. ++ */ ++ public boolean isSelfSignedCertificateEnabled() { ++ return selfSignedCertificateEnabled; ++ } ++ ++ /** ++ * Sets if self-signed certificates are going to be accepted. By default ++ * this option is disabled. ++ * ++ * @param selfSignedCertificateEnabled if self-signed certificates are going to be accepted. ++ */ ++ public void setSelfSignedCertificateEnabled(boolean selfSignedCertificateEnabled) { ++ this.selfSignedCertificateEnabled = selfSignedCertificateEnabled; ++ } ++ ++ /** ++ * Returns true if certificates presented by the server are going to be checked for their ++ * validity. By default certificates are not verified. ++ * ++ * @return true if certificates presented by the server are going to be checked for their ++ * validity. ++ */ ++ public boolean isExpiredCertificatesCheckEnabled() { ++ return expiredCertificatesCheckEnabled; ++ } ++ ++ /** ++ * Sets if certificates presented by the server are going to be checked for their ++ * validity. By default certificates are not verified. ++ * ++ * @param expiredCertificatesCheckEnabled if certificates presented by the server are going ++ * to be checked for their validity. ++ */ ++ public void setExpiredCertificatesCheckEnabled(boolean expiredCertificatesCheckEnabled) { ++ this.expiredCertificatesCheckEnabled = expiredCertificatesCheckEnabled; ++ } ++ ++ /** ++ * Returns true if certificates presented by the server are going to be checked for their ++ * domain. By default certificates are not verified. ++ * ++ * @return true if certificates presented by the server are going to be checked for their ++ * domain. ++ */ ++ public boolean isNotMatchingDomainCheckEnabled() { ++ return notMatchingDomainCheckEnabled; ++ } ++ ++ /** ++ * Sets if certificates presented by the server are going to be checked for their ++ * domain. By default certificates are not verified. ++ * ++ * @param notMatchingDomainCheckEnabled if certificates presented by the server are going ++ * to be checked for their domain. ++ */ ++ public void setNotMatchingDomainCheckEnabled(boolean notMatchingDomainCheckEnabled) { ++ this.notMatchingDomainCheckEnabled = notMatchingDomainCheckEnabled; ++ } ++ ++ /** ++ * Gets the custom SSLContext for SSL sockets. This is null by default. ++ * ++ * @return the SSLContext previously set with setCustomSSLContext() or null. ++ */ ++ public SSLContext getCustomSSLContext() { ++ return this.customSSLContext; ++ } ++ ++ /** ++ * Sets a custom SSLContext for creating SSL sockets. A custom Context causes all other ++ * SSL/TLS realted settings to be ignored. ++ * ++ * @param context the custom SSLContext for new sockets; null to reset default behaviour. ++ */ ++ public void setCustomSSLContext(SSLContext context) { ++ this.customSSLContext = context; ++ } ++ ++ /** ++ * Returns true if the connection is going to use stream compression. Stream compression ++ * will be requested after TLS was established (if TLS was enabled) and only if the server ++ * offered stream compression. With stream compression network traffic can be reduced ++ * up to 90%. By default compression is disabled. ++ * ++ * @return true if the connection is going to use stream compression. ++ */ ++ public boolean isCompressionEnabled() { ++ return compressionEnabled; ++ } ++ ++ /** ++ * Sets if the connection is going to use stream compression. Stream compression ++ * will be requested after TLS was established (if TLS was enabled) and only if the server ++ * offered stream compression. With stream compression network traffic can be reduced ++ * up to 90%. By default compression is disabled. ++ * ++ * @param compressionEnabled if the connection is going to use stream compression. ++ */ ++ public void setCompressionEnabled(boolean compressionEnabled) { ++ this.compressionEnabled = compressionEnabled; ++ } ++ ++ /** ++ * Returns true if the client is going to use SASL authentication when logging into the ++ * server. If SASL authenticatin fails then the client will try to use non-sasl authentication. ++ * By default SASL is enabled. ++ * ++ * @return true if the client is going to use SASL authentication when logging into the ++ * server. ++ */ ++ public boolean isSASLAuthenticationEnabled() { ++ return saslAuthenticationEnabled; ++ } ++ ++ /** ++ * Sets whether the client will use SASL authentication when logging into the ++ * server. If SASL authenticatin fails then the client will try to use non-sasl authentication. ++ * By default, SASL is enabled. ++ * ++ * @param saslAuthenticationEnabled if the client is going to use SASL authentication when ++ * logging into the server. ++ */ ++ public void setSASLAuthenticationEnabled(boolean saslAuthenticationEnabled) { ++ this.saslAuthenticationEnabled = saslAuthenticationEnabled; ++ } ++ ++ /** ++ * Returns true if the new connection about to be establish is going to be debugged. By ++ * default the value of {@link Connection#DEBUG_ENABLED} is used. ++ * ++ * @return true if the new connection about to be establish is going to be debugged. ++ */ ++ public boolean isDebuggerEnabled() { ++ return debuggerEnabled; ++ } ++ ++ /** ++ * Sets if the new connection about to be establish is going to be debugged. By ++ * default the value of {@link Connection#DEBUG_ENABLED} is used. ++ * ++ * @param debuggerEnabled if the new connection about to be establish is going to be debugged. ++ */ ++ public void setDebuggerEnabled(boolean debuggerEnabled) { ++ this.debuggerEnabled = debuggerEnabled; ++ } ++ ++ /** ++ * Sets if the reconnection mechanism is allowed to be used. By default ++ * reconnection is allowed. ++ * ++ * @param isAllowed if the reconnection mechanism is allowed to use. ++ */ ++ public void setReconnectionAllowed(boolean isAllowed) { ++ this.reconnectionAllowed = isAllowed; ++ } ++ /** ++ * Returns if the reconnection mechanism is allowed to be used. By default ++ * reconnection is allowed. ++ * ++ * @return if the reconnection mechanism is allowed to be used. ++ */ ++ public boolean isReconnectionAllowed() { ++ return this.reconnectionAllowed; ++ } ++ ++ /** ++ * Sets the socket factory used to create new xmppConnection sockets. ++ * This is useful when connecting through SOCKS5 proxies. ++ * ++ * @param socketFactory used to create new sockets. ++ */ ++ public void setSocketFactory(SocketFactory socketFactory) { ++ this.socketFactory = socketFactory; ++ } ++ ++ /** ++ * Sets if an initial available presence will be sent to the server. By default ++ * an available presence will be sent to the server indicating that this presence ++ * is not online and available to receive messages. If you want to log in without ++ * being 'noticed' then pass a false value. ++ * ++ * @param sendPresence true if an initial available presence will be sent while logging in. ++ */ ++ public void setSendPresence(boolean sendPresence) { ++ this.sendPresence = sendPresence; ++ } ++ ++ /** ++ * Returns true if the roster will be loaded from the server when logging in. This ++ * is the common behaviour for clients but sometimes clients may want to differ this ++ * or just never do it if not interested in rosters. ++ * ++ * @return true if the roster will be loaded from the server when logging in. ++ */ ++ public boolean isRosterLoadedAtLogin() { ++ return rosterLoadedAtLogin; ++ } ++ ++ /** ++ * Sets if the roster will be loaded from the server when logging in. This ++ * is the common behaviour for clients but sometimes clients may want to differ this ++ * or just never do it if not interested in rosters. ++ * ++ * @param rosterLoadedAtLogin if the roster will be loaded from the server when logging in. ++ */ ++ public void setRosterLoadedAtLogin(boolean rosterLoadedAtLogin) { ++ this.rosterLoadedAtLogin = rosterLoadedAtLogin; ++ } ++ ++ /** ++ * Returns a CallbackHandler to obtain information, such as the password or ++ * principal information during the SASL authentication. A CallbackHandler ++ * will be used ONLY if no password was specified during the login while ++ * using SASL authentication. ++ * ++ * @return a CallbackHandler to obtain information, such as the password or ++ * principal information during the SASL authentication. ++ */ ++ public CallbackHandler getCallbackHandler() { ++ return callbackHandler; ++ } ++ ++ /** ++ * Sets a CallbackHandler to obtain information, such as the password or ++ * principal information during the SASL authentication. A CallbackHandler ++ * will be used ONLY if no password was specified during the login while ++ * using SASL authentication. ++ * ++ * @param callbackHandler to obtain information, such as the password or ++ * principal information during the SASL authentication. ++ */ ++ public void setCallbackHandler(CallbackHandler callbackHandler) { ++ this.callbackHandler = callbackHandler; ++ } ++ ++ /** ++ * Returns the socket factory used to create new xmppConnection sockets. ++ * This is useful when connecting through SOCKS5 proxies. ++ * ++ * @return socketFactory used to create new sockets. ++ */ ++ public SocketFactory getSocketFactory() { ++ return this.socketFactory; ++ } ++ ++ /** ++ * An enumeration for TLS security modes that are available when making a connection ++ * to the XMPP server. ++ */ ++ public static enum SecurityMode { ++ ++ /** ++ * Securirty via TLS encryption is required in order to connect. If the server ++ * does not offer TLS or if the TLS negotiaton fails, the connection to the server ++ * will fail. ++ */ ++ required, ++ ++ /** ++ * Security via TLS encryption is used whenever it's available. This is the ++ * default setting. ++ */ ++ enabled, ++ ++ /** ++ * Security via TLS encryption is disabled and only un-encrypted connections will ++ * be used. If only TLS encryption is available from the server, the connection ++ * will fail. ++ */ ++ disabled ++ } ++ ++ /** ++ * Returns the username to use when trying to reconnect to the server. ++ * ++ * @return the username to use when trying to reconnect to the server. ++ */ ++ String getUsername() { ++ return this.username; ++ } ++ ++ /** ++ * Returns the password to use when trying to reconnect to the server. ++ * ++ * @return the password to use when trying to reconnect to the server. ++ */ ++ String getPassword() { ++ return this.password; ++ } ++ ++ /** ++ * Returns the resource to use when trying to reconnect to the server. ++ * ++ * @return the resource to use when trying to reconnect to the server. ++ */ ++ String getResource() { ++ return resource; ++ } ++ ++ boolean isRosterVersioningAvailable(){ ++ return isRosterVersioningAvailable; ++ } ++ ++ void setRosterVersioningAvailable(boolean enabled){ ++ isRosterVersioningAvailable = enabled; ++ } ++ ++ boolean isEntityCapsEnabled() { ++ return enableEntityCaps; ++ } ++ ++ /** ++ * Enable or disable Entity Capabilities (XEP-115) ++ * http://xmpp.org/extensions/xep-0115.html ++ * ++ * Only available on XMPPConnection ++ * default is enabled ++ * ++ * @param enabled ++ */ ++ void setEntityCaps(boolean enabled) { ++ enableEntityCaps = enabled; ++ } ++ ++ void setCapsNode(String node){ ++ capsNode = node; ++ } ++ ++ String getCapsNode(){ ++ return capsNode; ++ } ++ ++ /** ++ * Returns true if an available presence should be sent when logging in while reconnecting. ++ * ++ * @return true if an available presence should be sent when logging in while reconnecting ++ */ ++ boolean isSendPresence() { ++ return sendPresence; ++ } ++ ++ void setLoginInfo(String username, String password, String resource) { ++ this.username = username; ++ this.password = password; ++ this.resource = resource; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/ConnectionCreationListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/ConnectionCreationListener.java +new file mode 100644 +index 0000000..7cbda18 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/ConnectionCreationListener.java +@@ -0,0 +1,41 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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; ++ ++/** ++ * Implementors of this interface will be notified when a new {@link Connection} ++ * has been created. The newly created connection will not be actually connected to ++ * the server. Use {@link Connection#addConnectionCreationListener(ConnectionCreationListener)} ++ * to add new listeners. ++ * ++ * @author Gaston Dombiak ++ */ ++public interface ConnectionCreationListener { ++ ++ /** ++ * Notification that a new connection has been created. The new connection ++ * will not yet be connected to the server. ++ * ++ * @param connection the newly created connection. ++ */ ++ public void connectionCreated(Connection connection); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/ConnectionListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/ConnectionListener.java +new file mode 100644 +index 0000000..a7ceef1 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/ConnectionListener.java +@@ -0,0 +1,69 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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; ++ ++/** ++ * Interface that allows for implementing classes to listen for connection closing ++ * and reconnection events. Listeners are registered with Connection objects. ++ * ++ * @see Connection#addConnectionListener ++ * @see Connection#removeConnectionListener ++ * ++ * @author Matt Tucker ++ */ ++public interface ConnectionListener { ++ ++ /** ++ * Notification that the connection was closed normally or that the reconnection ++ * process has been aborted. ++ */ ++ public void connectionClosed(); ++ ++ /** ++ * Notification that the connection was closed due to an exception. When ++ * abruptly disconnected it is possible for the connection to try reconnecting ++ * to the server. ++ * ++ * @param e the exception. ++ */ ++ public void connectionClosedOnError(Exception e); ++ ++ /** ++ * The connection will retry to reconnect in the specified number of seconds. ++ * ++ * @param seconds remaining seconds before attempting a reconnection. ++ */ ++ public void reconnectingIn(int seconds); ++ ++ /** ++ * The connection has reconnected successfully to the server. Connections will ++ * reconnect to the server when the previous socket connection was abruptly closed. ++ */ ++ public void reconnectionSuccessful(); ++ ++ /** ++ * An attempt to connect to the server has failed. The connection will keep trying ++ * reconnecting to the server in a moment. ++ * ++ * @param e the exception that caused the reconnection to fail. ++ */ ++ public void reconnectionFailed(Exception e); ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/JmDNSPresenceDiscoverer.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/JmDNSPresenceDiscoverer.java +new file mode 100644 +index 0000000..c084acb +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/JmDNSPresenceDiscoverer.java +@@ -0,0 +1,131 @@ ++/* ++ * Copyright 2009 Jonas Ã…dahl. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.util.Tuple; ++ ++import org.jmdns.JmDNS; ++import org.jmdns.ServiceEvent; ++import org.jmdns.ServiceListener; ++ ++import java.io.IOException; ++import java.io.UnsupportedEncodingException; ++import java.util.List; ++import java.util.LinkedList; ++ ++ ++/** ++ * An implementation of LLPresenceDiscoverer using JmDNS. ++ * ++ * @author Jonas Ã…dahl ++ */ ++class JmDNSPresenceDiscoverer extends LLPresenceDiscoverer { ++ protected static final int SERVICE_REQUEST_TIMEOUT = 10000; ++ protected static JmDNS jmdns; ++ ++ JmDNSPresenceDiscoverer() throws XMPPException { ++ jmdns = JmDNSService.jmdns; ++ if (jmdns == null) ++ throw new XMPPException("Failed to fully initiate mDNS daemon."); ++ ++ jmdns.addServiceListener(JmDNSService.SERVICE_TYPE, new PresenceServiceListener()); ++ } ++ ++ /** ++ * Convert raw TXT fields to a list of strings. ++ * The raw TXT fields are encoded as follows: ++ *

    ++ *
  • Byte 0 specifies the length of the first field (which starts at byte 1).
  • ++ *
  • If the last byte of the previous field is the last byte of the array, ++ * all TXT fields has been read.
  • ++ *
  • If there are more bytes following, the next byte after the last of the ++ * previous field specifies the length of the next field.
  • ++ *
++ * ++ * @param bytes raw TXT fields as an array of bytes. ++ * @return TXT fields as a list of strings. ++ */ ++ private static List TXTToList(byte[] bytes) { ++ List list = new LinkedList(); ++ int size_i = 0; ++ while (size_i < bytes.length) { ++ int s = (int)(bytes[size_i]); ++ try { ++ list.add(new String(bytes, ++size_i, s, "UTF-8")); ++ } catch (UnsupportedEncodingException uee) { ++ // ignore ++ } ++ size_i += s; ++ } ++ return list; ++ } ++ ++ /** ++ * Convert a TXT field list bundled with a '_presence._tcp' service to a ++ * String,String tuple. The TXT field list looks as following: ++ * "key=value" which is converted into the tuple (key, value). ++ * ++ * @param strings the TXT fields. ++ * @return a list of key,value tuples. ++ */ ++ private static List> TXTListToXMPPRecords(List strings) { ++ // records :: [(String, String)] ++ List> records = new LinkedList>(); ++ for (String s : strings) { ++ String[] record = s.split("=", 2); ++ // check if valid ++ if (record.length == 2) ++ records.add(new Tuple(record[0], record[1])); ++ } ++ return records; ++ } ++ ++ /** ++ * Implementation of a JmDNS ServiceListener. Listens to service resolved and ++ * service information resolved events. ++ */ ++ private class PresenceServiceListener implements ServiceListener { ++ public void serviceAdded(ServiceEvent event) { ++ // XXX ++ // To reduce network usage, we should only request information ++ // when needed. ++ new RequestInfoThread(event).start(); ++ } ++ public void serviceRemoved(ServiceEvent event) { ++ presenceRemoved(event.getName()); ++ } ++ public void serviceResolved(ServiceEvent event) { ++ presenceInfoAdded(event.getName(), ++ new LLPresence(event.getName(), ++ event.getInfo().getAddress(), event.getInfo().getPort(), ++ TXTListToXMPPRecords(TXTToList(event.getInfo().getTextBytes())))); ++ } ++ ++ private class RequestInfoThread extends Thread { ++ ServiceEvent event; ++ ++ RequestInfoThread(ServiceEvent event) { ++ this.event = event; ++ } ++ ++ public void run() { ++ jmdns.requestServiceInfo(event.getType(), event.getName(), ++ true, SERVICE_REQUEST_TIMEOUT); ++ } ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/JmDNSService.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/JmDNSService.java +new file mode 100644 +index 0000000..3ab73e5 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/JmDNSService.java +@@ -0,0 +1,195 @@ ++/* ++ * Copyright 2009 Jonas Ã…dahl. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.util.Tuple; ++ ++import org.jmdns.JmDNS; ++import org.jmdns.ServiceInfo; ++import org.jmdns.ServiceNameListener; ++import org.jmdns.impl.JmDNSImpl; ++import org.jmdns.impl.DNSCache; ++import org.jmdns.impl.DNSEntry; ++ ++import java.util.LinkedList; ++import java.net.InetAddress; ++import java.io.IOException; ++import java.util.Hashtable; ++ ++/** ++ * Implements a LLService using JmDNS. ++ * ++ * @author Jonas Ã…dahl ++ */ ++public class JmDNSService extends LLService implements ServiceNameListener { ++ static JmDNS jmdns = null; ++ static JmDNSPresenceDiscoverer presenceDiscoverer = null; ++ private ServiceInfo serviceInfo; ++ static final String SERVICE_TYPE = "_presence._tcp.local."; ++ ++ private JmDNSService(LLPresence presence, LLPresenceDiscoverer presenceDiscoverer) { ++ super(presence, presenceDiscoverer); ++ } ++ ++ /** ++ * Instantiate a new JmDNSService and start to listen for connections. ++ * ++ * @param presence the mDNS presence information that should be used. ++ */ ++ public static LLService create(LLPresence presence) throws XMPPException { ++ return create(presence, null); ++ } ++ ++ /** ++ * Instantiate a new JmDNSService and start to listen for connections. ++ * ++ * @param presence the mDNS presence information that should be used. ++ * @param addr the INET Address to use. ++ */ ++ public static LLService create(LLPresence presence, InetAddress addr) throws XMPPException { ++ // Start the JmDNS daemon. ++ initJmDNS(addr); ++ ++ // Start the presence discoverer ++ JmDNSPresenceDiscoverer presenceDiscoverer = new JmDNSPresenceDiscoverer(); ++ ++ // Start the presence service ++ JmDNSService service = new JmDNSService(presence, presenceDiscoverer); ++ ++ return service; ++ } ++ ++ @Override ++ public void close() { ++ super.close(); ++ jmdns.close(); ++ jmdns = null; ++ } ++ ++ /** ++ * Start the JmDNS daemon. ++ */ ++ private static void initJmDNS(InetAddress addr) throws XMPPException { ++ try { ++ if (jmdns == null) { ++ if (addr == null) { ++ jmdns = JmDNS.create(); ++ } ++ else { ++ jmdns = JmDNS.create(addr); ++ } ++ } ++ } ++ catch (IOException ioe) { ++ throw new XMPPException(ioe); ++ } ++ } ++ ++ protected void updateText() { ++ Hashtable ht = new Hashtable(); ++ ++ for (Tuple t : presence.toList()) { ++ if (t.a != null && t.b != null) { ++ ht.put(t.a, t.b); ++ } ++ } ++ ++ serviceInfo.setText(ht); ++ } ++ ++ /** ++ * Register the DNS-SD service with the daemon. ++ */ ++ protected void registerService() throws XMPPException { ++ Hashtable ht = new Hashtable(); ++ ++ for (Tuple t : presence.toList()) { ++ if (t.a != null && t.b != null) ++ ht.put(t.a, t.b); ++ } ++ serviceInfo = ServiceInfo.create(SERVICE_TYPE, ++ presence.getServiceName(), presence.getPort(), 0, 0, ht); ++ serviceInfo.addServiceNameListener(this); ++ ++ try { ++ String originalName = serviceInfo.getQualifiedName(); ++ jmdns.registerService(serviceInfo); ++ presence.setServiceName(serviceInfo.getName()); ++ ++ if (!originalName.equals(serviceInfo.getQualifiedName())) { ++ // Update presence service name ++ // Name collision occured, lets remove confusing elements ++ // from cache in case something goes wrong ++ JmDNSImpl jmdnsimpl = (JmDNSImpl) jmdns; ++ DNSCache.CacheNode n = jmdnsimpl.getCache().find(originalName); ++ ++ LinkedList toRemove = new LinkedList(); ++ while (n != null) { ++ DNSEntry e = n.getValue(); ++ if (e != null) ++ toRemove.add(e); ++ ++ n = n.next(); ++ } ++ ++ // Remove the DNSEntry's one by one ++ for (DNSEntry e : toRemove) { ++ jmdnsimpl.getCache().remove(e); ++ } ++ } ++ } ++ catch (IOException ioe) { ++ throw new XMPPException(ioe); ++ } ++ } ++ ++ /** ++ * Reregister the DNS-SD service with the daemon. ++ */ ++ protected void reannounceService() throws XMPPException { ++ try { ++ jmdns.reannounceService(serviceInfo); ++ } ++ catch (IOException ioe) { ++ throw new XMPPException("Exception occured when reannouncing mDNS presence.", ioe); ++ } ++ } ++ ++ public void serviceNameChanged(String newName, String oldName) { ++ try { ++ super.serviceNameChanged(newName, oldName); ++ } ++ catch (Throwable t) { ++ // ignore ++ } ++ } ++ ++ /** ++ * Unregister the DNS-SD service, making the client unavailable. ++ */ ++ public void makeUnavailable() { ++ jmdns.unregisterService(serviceInfo); ++ serviceInfo = null; ++ } ++ ++ ++ @Override ++ public void spam() { ++ super.spam(); ++ System.out.println("Service name: " + serviceInfo.getName()); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLChat.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLChat.java +new file mode 100644 +index 0000000..2df8d1a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLChat.java +@@ -0,0 +1,114 @@ ++/* ++ * Copyright 2009 Jonas Ã…dahl. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.packet.Message; ++ ++import java.util.Set; ++import java.util.List; ++import java.util.LinkedList; ++import java.util.concurrent.CopyOnWriteArraySet; ++ ++/** ++ * Keeps track of a chat session between two link-local clients. ++ */ ++public class LLChat { ++ private String serviceName; ++ private LLService service; ++ ++ private Set listeners = new CopyOnWriteArraySet(); ++ ++ // Queue for storing messages in case no listener is available when a ++ // message is to be delivered. ++ private List messageQueue = new LinkedList(); ++ ++ ++ LLChat(LLService service, LLPresence presence) throws XMPPException { ++ this.service = service;; ++ serviceName = presence.getServiceName(); ++ } ++ ++ /** ++ * Get the service name of the remote client of this chat session. ++ * ++ * @return the service name of the remote client of this chat session ++ */ ++ public String getServiceName() { ++ return serviceName; ++ } ++ ++ /** ++ * Deliver a message to the message listeners. ++ * ++ * @param message the message to be delivered. ++ */ ++ void deliver(Message message) { ++ // if no listeners are available, queue the messages for later. ++ synchronized (listeners) { ++ if (listeners.isEmpty()) ++ messageQueue.add(message); ++ else { ++ for (LLMessageListener listener : listeners) { ++ listener.processMessage(this, message); ++ } ++ } ++ } ++ } ++ ++ /** ++ * Send a message packet to the remote client. ++ * ++ * @param message the message to be sent. ++ * @throws XMPPException if an exception occurs during transmission. ++ */ ++ public void sendMessage(Message message) throws XMPPException { ++ message.setTo(serviceName); ++ message.setType(Message.Type.chat); ++ service.sendMessage(message); ++ } ++ ++ public Message generateMessage(String text) { ++ Message message = new Message(serviceName, Message.Type.chat); ++ message.setBody(text); ++ return message; ++ } ++ ++ /** ++ * Send a message to the remote client. ++ * ++ * @param message the message to be sent. ++ * @throws XMPPException if an exception occurs during transmission. ++ */ ++ public void sendMessage(String text) throws XMPPException { ++ service.sendMessage(generateMessage(text)); ++ } ++ ++ /** ++ * Add a message listener. The message listener will be notified when new ++ * messages are received. If there was no listener when messages was to be ++ * delivered, the first listener to be added will receive all of the queued ++ * messages. ++ */ ++ public void addMessageListener(LLMessageListener listener) { ++ synchronized (listeners) { ++ listeners.add(listener); ++ for (Message message : messageQueue) ++ listener.processMessage(this, message); ++ messageQueue.clear(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLChatListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLChatListener.java +new file mode 100644 +index 0000000..8f2c0c6 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLChatListener.java +@@ -0,0 +1,36 @@ ++/* ++ * Copyright 2009 Jonas Ã…dahl. ++ * ++ * All rights reserved. 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; ++ ++/** ++ * Notification about new Link-local chat sessions. ++ */ ++public interface LLChatListener { ++ ++ /** ++ * New chat has been created. ++ * ++ * @param chat the newly created chat. ++ */ ++ public void newChat(LLChat chat); ++ ++ /** ++ * Called when a chat session is invalidated (due to service ++ * name changes. ++ */ ++ public void chatInvalidated(LLChat chat); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLConnectionConfiguration.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLConnectionConfiguration.java +new file mode 100644 +index 0000000..8dbc75b +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLConnectionConfiguration.java +@@ -0,0 +1,107 @@ ++/* ++ * Copyright 2009 Jonas Ã…dahl. ++ * ++ * All rights reserved. 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 javax.net.SocketFactory; ++import java.net.Socket; ++ ++/** ++ * Link-local connection configuration settings. Two general cases exists, ++ * one where the we want to connect to a remote peer, and one when o remote ++ * peer has connected to us. ++ */ ++public class LLConnectionConfiguration extends ConnectionConfiguration implements Cloneable { ++ private LLPresence remotePresence; ++ private LLPresence localPresence; ++ private Socket socket; ++ ++ private boolean debuggerEnabled = Connection.DEBUG_ENABLED; ++ ++ // Holds the socket factory that is used to generate the socket in the connection ++ private SocketFactory socketFactory; ++ ++ /** ++ * Configuration used for connecting to remote peer. ++ * @param local LLPresence for the local user ++ * @param remote LLPresence for the remote user ++ */ ++ LLConnectionConfiguration(LLPresence local, LLPresence remote) { ++ this.localPresence = local; ++ this.remotePresence = remote; ++ } ++ ++ /** ++ * Instantiate a link-local configuration when the connection is acting as ++ * the host. ++ * ++ * @param local the local link-local presence class. ++ * @param remoteSocket the socket which the new connection is assigned to. ++ */ ++ LLConnectionConfiguration(LLPresence local, Socket remoteSocket) { ++ this.localPresence = local; ++ this.socket = remoteSocket; ++ } ++ ++ /** ++ * Tells if the connection is the initiating one. ++ * @return true if this configuration is for the connecting connection. ++ */ ++ public boolean isInitiator() { ++ return socket == null; ++ } ++ ++ /** ++ * Return the service name of the remote peer. ++ * @return the remote peer's service name. ++ */ ++ public String getRemoteServiceName() { ++ return remotePresence.getServiceName(); ++ } ++ ++ /** ++ * Return the service name of this client. ++ * @return this clients service name. ++ */ ++ public String getLocalServiceName() { ++ return localPresence.getServiceName(); ++ } ++ ++ /** ++ * Return this clients link-local presence information. ++ * @return this clients link-local presence information. ++ */ ++ public LLPresence getLocalPresence() { ++ return localPresence; ++ } ++ ++ /** ++ * Return the remote client's link-local presence information. ++ * @return the remote client's link-local presence information. ++ */ ++ public LLPresence getRemotePresence() { ++ return remotePresence; ++ } ++ ++ /** ++ * Return the socket which has been established when the ++ * remote client connected. ++ * @return the socket established when the remote client connected. ++ */ ++ public Socket getSocket() { ++ return socket; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLConnectionListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLConnectionListener.java +new file mode 100644 +index 0000000..c4f4681 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLConnectionListener.java +@@ -0,0 +1,30 @@ ++/* ++ * Copyright 2009 Jonas Ã…dahl. ++ * ++ * All rights reserved. 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; ++ ++/** ++ * Notification about when new Link-local connections has been established. ++ */ ++public interface LLConnectionListener { ++ ++ /** ++ * A new link-local connection has been established. ++ * ++ * @param connection the new established connection. ++ */ ++ public void connectionCreated(XMPPLLConnection connection); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLMessageListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLMessageListener.java +new file mode 100644 +index 0000000..fb97c74 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLMessageListener.java +@@ -0,0 +1,32 @@ ++/* ++ * Copyright 2009 Jonas Ã…dahl. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.packet.Message; ++ ++/** ++ * Notification when messages are being delivered to a chat. ++ */ ++public interface LLMessageListener { ++ /** ++ * New message in chat. ++ * ++ * @param chat the chat session which the message was delivered to. ++ * @param message the message being delivered. ++ */ ++ void processMessage(LLChat chat, Message message); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLPacketReader.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLPacketReader.java +new file mode 100644 +index 0000000..bb88e5e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLPacketReader.java +@@ -0,0 +1,244 @@ ++/** ++ * $RCSfile$ ++ * $Revision: 7232 $ ++ * $Date: 2007-02-20 16:57:31 -0800 (Tue, 20 Feb 2007) $ ++ * ++ * Copyright 2003-2007 Jive Software. 2008-2009 Jonas Ã…dahl ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.packet.*; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smack.provider.ProviderManager; ++import org.jivesoftware.smack.util.PacketParserUtils; ++import org.xmlpull.v1.XmlPullParserFactory; ++import org.xmlpull.v1.XmlPullParser; ++import org.xmlpull.v1.XmlPullParserException; ++ ++import java.io.IOException; ++import java.util.*; ++import java.util.concurrent.*; ++ ++/** ++ * Listens for XML traffic from a remote XMPP client and parses it into packet objects. ++ * The packet reader also manages all packet listeners and collectors.

++ * ++ * @see PacketCollector ++ * @see PacketListener ++ * @author Matt Tucker ++ */ ++public class LLPacketReader extends PacketReader { ++ ++ private XMPPLLConnection connection; ++ private LLService service; ++ ++ public LLPacketReader(final LLService service, final XMPPLLConnection connection) { ++ super(connection); ++ this.service = service; ++ this.connection = connection; ++ this.init(); ++ } ++ ++ /** ++ * Parse top-level packets in order to process them further. ++ * ++ * @param thread the thread that is being used by the reader to parse incoming packets. ++ */ ++ protected void parsePackets(Thread thread) { ++ try { ++ int eventType = parser.getEventType(); ++ do { ++ connection.updateLastActivity(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("message")) { ++ processPacket(PacketParserUtils.parseMessage(parser)); ++ } ++ else if (parser.getName().equals("iq")) { ++ processPacket(parseIQ(parser)); ++ } ++ else if (parser.getName().equals("presence")) { ++ processPacket(PacketParserUtils.parsePresence(parser)); ++ } ++ // We found an opening stream. Record information about it, then notify ++ // the connectionID lock so that the packet reader startup can finish. ++ else if (parser.getName().equals("stream")) { ++ // Ensure the correct jabber:client namespace is being used. ++ if ("jabber:client".equals(parser.getNamespace(null))) { ++ // Get the connection id. ++ for (int i=0; i rest = ++ new ConcurrentHashMap(); ++ ++ public static enum Mode { ++ avail, away, dnd ++ } ++ ++ // Host details ++ private int port = 0; ++ private String host; ++ private String host6; // Currently unused ++ private String serviceName; ++ ++ public LLPresence(String serviceName) { ++ this.serviceName = serviceName; ++ } ++ ++ public LLPresence(String serviceName, InetAddress host, int port) { ++ this.serviceName = serviceName; ++ if (host instanceof Inet4Address) { ++ this.host = host.getHostAddress(); ++ } else if (host instanceof Inet6Address) { ++ this.host6 = host.getHostAddress(); ++ } else { ++ throw new UnsupportedOperationException(); ++ } ++ this.port = port; ++ } ++ ++ public LLPresence(String serviceName, InetAddress host, int port, ++ List> records) { ++ this(serviceName, host, port); ++ ++ // Parse the tuple list (originating from the TXT fields) and put them ++ // in variables ++ for (Tuple t : records) { ++ if (t.a.equals("1st")) ++ setFirstName(t.b); ++ else if (t.a.equals("last")) ++ setLastName(t.b); ++ else if (t.a.equals("email")) ++ setEMail(t.b); ++ else if (t.a.equals("jid")) ++ setJID(t.b); ++ else if (t.a.equals("nick")) ++ setNick(t.b); ++ else if (t.a.equals("hash")) ++ setHash(t.b); ++ else if (t.a.equals("node")) ++ setNode(t.b); ++ else if (t.a.equals("ver")) ++ setVer(t.b); ++ else if (t.a.equals("status")) { ++ try { ++ setStatus(Mode.valueOf(t.b)); ++ } ++ catch (IllegalArgumentException iae) { ++ System.err.println("Found invalid presence status (" + ++ t.b + ") in TXT entry."); ++ } ++ } ++ else if (t.a.equals("msg")) ++ setMsg(t.b); ++ else { ++ // Unknown key ++ if (!rest.containsKey(t.a)) ++ rest.put(t.a, t.b); ++ } ++ } ++ } ++ ++ public List> toList() { ++ LinkedList> list = new LinkedList>(); ++ list.add(new Tuple("txtvers", "1")); ++ list.add(new Tuple("1st", firstName)); ++ list.add(new Tuple("last", lastName)); ++ list.add(new Tuple("email", email)); ++ list.add(new Tuple("jid", jid)); ++ list.add(new Tuple("nick", nick)); ++ list.add(new Tuple("status", status.toString())); ++ list.add(new Tuple("msg", msg)); ++ list.add(new Tuple("hash", hash)); ++ list.add(new Tuple("node", node)); ++ list.add(new Tuple("ver", ver)); ++ list.add(new Tuple("port.p2ppj", new Integer(port).toString())); ++ ++ for (Map.Entry e : rest.entrySet()) { ++ list.add(new Tuple(e.getKey(), e.getValue())); ++ } ++ ++ return list; ++ } ++ ++ /** ++ * Update all the values of the presence. ++ */ ++ void update(LLPresence p) { ++ setFirstName(p.getFirstName()); ++ setLastName(p.getLastName()); ++ setEMail(p.getEMail()); ++ setMsg(p.getMsg()); ++ setNick(p.getNick()); ++ setStatus(p.getStatus()); ++ setJID(p.getJID()); ++ } ++ ++ public void setServiceName(String serviceName) { ++ this.serviceName = serviceName; ++ } ++ ++ public void setFirstName(String name) { ++ firstName = name; ++ } ++ ++ public void setLastName(String name) { ++ lastName = name; ++ } ++ ++ public void setEMail(String email) { ++ this.email = email; ++ } ++ ++ public void setMsg(String msg) { ++ this.msg = msg; ++ } ++ ++ public void setNick(String nick) { ++ this.nick = nick; ++ } ++ ++ public void setStatus(Mode status) { ++ this.status = status; ++ } ++ ++ public void setJID(String jid) { ++ this.jid = jid; ++ } ++ ++ public void setHash(String hash) { ++ this.hash = hash; ++ } ++ ++ public void setNode(String node) { ++ this.node = node; ++ } ++ ++ public void setVer(String ver) { ++ this.ver = ver; ++ } ++ ++ void setPort(int port) { ++ this.port = port; ++ } ++ ++ public String getFirstName() { ++ return firstName; ++ } ++ ++ public String getLastName() { ++ return lastName; ++ } ++ ++ public String getEMail() { ++ return email; ++ } ++ ++ public String getMsg() { ++ return msg; ++ } ++ ++ public String getNick() { ++ return nick; ++ } ++ ++ public Mode getStatus() { ++ return status; ++ } ++ ++ public String getJID() { ++ return jid; ++ } ++ ++ public String getServiceName() { ++ return serviceName; ++ } ++ ++ public String getHost() { ++ return host; ++ } ++ ++ public String getHash() { ++ return hash; ++ } ++ ++ public String getNode() { ++ return node; ++ } ++ ++ public String getVer() { ++ return ver; ++ } ++ ++ public int getPort() { ++ return port; ++ } ++ ++ public String getValue(String key) { ++ return rest.get(key); ++ } ++ ++ public void putValue(String key, String value) { ++ rest.put(key, value); ++ } ++ ++ public boolean equals(Object o) { ++ if (o instanceof LLPresence) { ++ LLPresence p = (LLPresence)o; ++ return p.serviceName == serviceName && ++ p.host == host; ++ } ++ return false; ++ } ++ ++ public int hashCode() { ++ return serviceName.hashCode(); ++ } ++ ++ /** Update from an old presence, in case it has host information that we are missing (e.g. ipv4 address) */ ++ public void updateFrom(LLPresence other) { ++ if (other == null) ++ return; ++ ++ if (host6 == null) { ++ host6 = other.host6; ++ } ++ ++ if (host == null) { ++ host = other.host; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLPresenceDiscoverer.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLPresenceDiscoverer.java +new file mode 100644 +index 0000000..56bd013 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLPresenceDiscoverer.java +@@ -0,0 +1,108 @@ ++/* ++ * Copyright 2009 Jonas Ã…dahl. ++ * ++ * All rights reserved. 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.util.Set; ++import java.util.Collection; ++import java.util.Map; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.CopyOnWriteArraySet; ++ ++/** ++ * Link-local presence discoverer. XEP-0174 describes how to use mDNS/DNS-SD. ++ * This class in an abstract representation of the basic functionality of ++ * handeling presences discovering. ++ */ ++public abstract class LLPresenceDiscoverer { ++ // Listeners to be notified about changes. ++ protected Set listeners = new CopyOnWriteArraySet(); ++ // Map of service name -> Link-local presence ++ private Map presences = new ConcurrentHashMap(); ++ ++ /** ++ * Add listener which will be notified when new presences are discovered, ++ * presence information changed or presences goes offline. ++ * @param listener the listener to be notified. ++ */ ++ public void addPresenceListener(LLPresenceListener listener) { ++ listeners.add(listener); ++ for (LLPresence presence : presences.values()) ++ listener.presenceNew(presence); ++ } ++ ++ /** ++ * Remove presence listener. ++ * @param listener listener to be removed. ++ */ ++ public void removePresenceListener(LLPresenceListener listener) { ++ listeners.remove(listener); ++ } ++ ++ /** ++ * Return a collection of presences known. ++ * @return all known presences. ++ */ ++ public Collection getPresences() { ++ return presences.values(); ++ } ++ ++ /** ++ * Return the presence with the specified service name. ++ * ++ * @param name service name of the presence. ++ * @return the presence information with the given service name. ++ */ ++ public LLPresence getPresence(String name) { ++ return presences.get(name); ++ } ++ ++ /** ++ * Used by the class extending this one to tell when new ++ * presence is added. ++ * ++ * @param name service name of the presence. ++ */ ++ protected void presenceAdded(String name) { ++ presences.put(name, null); ++ } ++ ++ /** ++ * Used by the class extending this one to tell when new ++ * presence information is added. ++ * ++ * @param name service name of the presence. ++ * @param presence presence information. ++ */ ++ protected void presenceInfoAdded(String name, LLPresence presence) { ++ presence.updateFrom(presences.get(name)); ++ presences.put(name, presence); ++ for (LLPresenceListener l : listeners) ++ l.presenceNew(presence); ++ } ++ ++ /** ++ * Used by the class extending htis one to tell when a presence ++ * goes offline. ++ * ++ * @param name service name of the presence going offline. ++ */ ++ protected void presenceRemoved(String name) { ++ LLPresence presence = presences.remove(name); ++ for (LLPresenceListener l : listeners) ++ l.presenceRemove(presence); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLPresenceListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLPresenceListener.java +new file mode 100644 +index 0000000..c486468 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLPresenceListener.java +@@ -0,0 +1,35 @@ ++/* ++ * Copyright 2009 Jonas Ã…dahl. ++ * ++ * All rights reserved. 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; ++ ++/** ++ * Interface for receiving notifications about presence changes. ++ */ ++public interface LLPresenceListener { ++ /** ++ * New link-local presence has been discovered. ++ * ++ * @param presence information about the new presence ++ */ ++ ++ public void presenceNew(LLPresence presence); ++ /** ++ * A link-local presence has gone offline. ++ * @param presence the presence which went offline. ++ */ ++ public void presenceRemove(LLPresence presence); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLService.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLService.java +new file mode 100644 +index 0000000..21e2daf +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLService.java +@@ -0,0 +1,995 @@ ++/* ++ * Copyright 2009 Jonas Ã…dahl. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Message; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.AndFilter; ++import org.jivesoftware.smack.filter.OrFilter; ++import org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.filter.MessageTypeFilter; ++import org.jivesoftware.smack.filter.IQTypeFilter; ++ ++ ++import java.net.ServerSocket; ++import java.net.Socket; ++import java.net.SocketException; ++import java.io.IOException; ++import java.util.Collection; ++import java.util.HashSet; ++import java.util.Set; ++import java.util.Map; ++import java.util.LinkedList; ++import java.util.ArrayList; ++import java.util.concurrent.CopyOnWriteArraySet; ++import java.util.concurrent.ConcurrentHashMap; ++ ++/** ++ * LLService acts as an abstract interface to a Link-local XMPP service ++ * according to XEP-0174. XEP-0174 describes how this is achieved using ++ * mDNS/DNS-SD, and this class creates an implementation unspecific ++ * interface for doing this. ++ * ++ * The mDNS/DNS-SD is for example implemented by JmDNSService (using JmDNS). ++ * ++ * There is only one instance of LLService possible at one time. ++ * ++ * Tasks taken care of here are: ++ *

    ++ *
  • Connection Management ++ *
      ++ *
    • Keep track of active connections from and to the local client
    • ++ *
    • Listen for connections on a semi randomized port announced by the ++ * mDNS/DNS-SD daemon
    • ++ *
    • Establish new connections when there is none to use and packets are ++ * to be sent
    • ++ *
        ++ *
      • Chat Management - Keep track of messaging sessions between users
      • ++ *
      ++ * ++ * @author Jonas Ã…dahl ++ */ ++public abstract class LLService { ++ private LLService service = null; ++ ++ // Listeners for new services ++ private static Set serviceCreatedListeners = ++ new CopyOnWriteArraySet(); ++ ++ static final int DEFAULT_MIN_PORT = 2300; ++ static final int DEFAULT_MAX_PORT = 2400; ++ protected LLPresence presence; ++ private boolean done = false; ++ private Thread listenerThread; ++ ++ private boolean initiated = false; ++ ++ private Map chats = ++ new ConcurrentHashMap(); ++ ++ private Map ingoing = ++ new ConcurrentHashMap(); ++ private Map outgoing = ++ new ConcurrentHashMap(); ++ ++ // Listeners for state updates, such as LLService closed down ++ private Set stateListeners = ++ new CopyOnWriteArraySet(); ++ ++ // Listeners for XMPPLLConnections associated with this service ++ private Set llServiceConnectionListeners = ++ new CopyOnWriteArraySet(); ++ ++ // Listeners for packets coming from this Link-local service ++ private final Map listeners = ++ new ConcurrentHashMap(); ++ ++ // Presence discoverer, notifies of changes in presences on the network. ++ private LLPresenceDiscoverer presenceDiscoverer; ++ ++ // chat listeners gets notified when new chats are created ++ private Set chatListeners = new CopyOnWriteArraySet(); ++ ++ // Set of Packet collector wrappers ++ private Set collectorWrappers = ++ new CopyOnWriteArraySet(); ++ ++ // Set of associated connections. ++ private Set associatedConnections = ++ new HashSet(); ++ ++ private ServerSocket socket; ++ ++ static { ++ SmackConfiguration.getVersion(); ++ } ++ ++ /** ++ * Spam stdout with some debug information. ++ */ ++ public void spam() { ++ System.out.println("Number of ingoing connection in map: " + ingoing.size()); ++ System.out.println("Number of outgoing connection in map: " + outgoing.size()); ++ ++ System.out.println("Active chats:"); ++ for (LLChat chat : chats.values()) { ++ System.out.println(" * " + chat.getServiceName()); ++ } ++ ++ System.out.println("Known presences:"); ++ for (LLPresence presence : presenceDiscoverer.getPresences()) { ++ System.out.println(" * " + presence.getServiceName() + "(" + presence.getStatus() + ", " + presence.getHost() + ":" + presence.getPort() + ")"); ++ } ++ Thread.currentThread().getThreadGroup().list(); ++ } ++ ++ protected LLService(LLPresence presence, LLPresenceDiscoverer discoverer) { ++ this.presence = presence; ++ presenceDiscoverer = discoverer; ++ service = this; ++ ++ XMPPLLConnection.addLLConnectionListener(new LLConnectionListener() { ++ public void connectionCreated(XMPPLLConnection connection) { ++ // We only care about this connection if we were the one ++ // creating it ++ if (isAssociatedConnection(connection)) { ++ if (connection.isInitiator()) { ++ addOutgoingConnection(connection); ++ } ++ else { ++ addIngoingConnection(connection); ++ } ++ ++ connection.addConnectionListener(new ConnectionActivityListener(connection)); ++ ++ // Notify listeners that a new connection associated with this ++ // service has been created. ++ notifyNewServiceConnection(connection); ++ ++ // add message listener. filter logic: ++ // type = msg ^ (msg.type = chat v msg.type = normal v msg.type = error) ++ connection.addPacketListener(new MessageListener(), ++ new AndFilter( ++ new PacketTypeFilter(Message.class), ++ new OrFilter( ++ new MessageTypeFilter(Message.Type.chat), ++ new OrFilter( ++ new MessageTypeFilter(Message.Type.normal), ++ new MessageTypeFilter(Message.Type.error))))); ++ ++ // add other existing packet filters associated with this service ++ for (ListenerWrapper wrapper : listeners.values()) { ++ connection.addPacketListener(wrapper.getPacketListener(), ++ wrapper.getPacketFilter()); ++ } ++ ++ // add packet collectors ++ for (CollectorWrapper cw : collectorWrappers) { ++ cw.createPacketCollector(connection); ++ } ++ } ++ } ++ }); ++ ++ notifyServiceListeners(this); ++ } ++ ++ /** ++ * Add a LLServiceListener. The LLServiceListener is notified when a new ++ * Link-local service is created. ++ * ++ * @param listener the LLServiceListener ++ */ ++ public static void addLLServiceListener(LLServiceListener listener) { ++ serviceCreatedListeners.add(listener); ++ } ++ ++ /** ++ * Remove a LLServiceListener. ++ */ ++ public static void removeLLServiceListener(LLServiceListener listener) { ++ serviceCreatedListeners.remove(listener); ++ } ++ ++ /** ++ * Notify LLServiceListeners about a new Link-local service. ++ * ++ * @param service the new service. ++ */ ++ public static void notifyServiceListeners(LLService service) { ++ for (LLServiceListener listener : serviceCreatedListeners) { ++ listener.serviceCreated(service); ++ } ++ } ++ ++ /** ++ * Returns the running mDNS/DNS-SD XMPP instance. There can only be one ++ * instance at a time. ++ * ++ * @return the active LLService instance. ++ * @throws XMPPException if the LLService hasn't been instantiated. ++ */ ++ /*public static LLService getServiceInstance() throws XMPPException { ++ if (service == null) ++ throw new XMPPException("Link-local service not initiated."); ++ return service; ++ }*/ ++ ++ /** ++ * Registers the service to the mDNS/DNS-SD daemon. ++ * Should be implemented by the class extending this, for mDNS/DNS-SD library specific calls. ++ */ ++ protected abstract void registerService() throws XMPPException; ++ ++ /** ++ * Re-announce the presence information by using the mDNS/DNS-SD daemon. ++ */ ++ protected abstract void reannounceService() throws XMPPException; ++ ++ /** ++ * Make the client unavailabe. Equivalent to sending unavailable-presence. ++ */ ++ public abstract void makeUnavailable(); ++ ++ /** ++ * Update the text field information. Used for setting new presence information. ++ */ ++ protected abstract void updateText(); ++ ++ public void init() throws XMPPException { ++ // allocate a new port for remote clients to connect to ++ socket = bindRange(DEFAULT_MIN_PORT, DEFAULT_MAX_PORT); ++ presence.setPort(socket.getLocalPort()); ++ ++ // register service on the allocated port ++ registerService(); ++ ++ // start to listen for new connections ++ listenerThread = new Thread() { ++ public void run() { ++ try { ++ // Listen for connections ++ listenForConnections(); ++ ++ // If listen for connections returns with no exception, ++ // the service has closed down ++ for (LLServiceStateListener listener : stateListeners) ++ listener.serviceClosed(); ++ } catch (XMPPException e) { ++ for (LLServiceStateListener listener : stateListeners) ++ listener.serviceClosedOnError(e); ++ } ++ } ++ }; ++ listenerThread.setName("Smack Link-local Service Listener"); ++ listenerThread.setDaemon(true); ++ listenerThread.start(); ++ ++ initiated = true; ++ } ++ ++ public void close() { ++ done = true; ++ ++ // close incoming connections ++ for (XMPPLLConnection connection : ingoing.values()) { ++ try { ++ connection.shutdown(); ++ } catch (Exception e) { ++ // ignore ++ } ++ } ++ ++ // close outgoing connections ++ for (XMPPLLConnection connection : outgoing.values()) { ++ try { ++ connection.shutdown(); ++ } catch (Exception e) { ++ // ignore ++ } ++ } ++ try { ++ socket.close(); ++ } catch (IOException ioe) { ++ // ignore ++ } ++ } ++ ++ /** ++ * Listen for new connections on socket, and spawn XMPPLLConnections ++ * when new connections are established. ++ * ++ * @throws XMPPException whenever an exception occurs ++ */ ++ private void listenForConnections() throws XMPPException { ++ while (!done) { ++ try { ++ // wait for new connection ++ Socket s = socket.accept(); ++ ++ LLConnectionConfiguration config = ++ new LLConnectionConfiguration(presence, s); ++ XMPPLLConnection connection = new XMPPLLConnection(this, config); ++ ++ // Associate the new connection with this service ++ addAssociatedConnection(connection); ++ ++ // Spawn new thread to handle the connecting. ++ // The reason for spawning a new thread is to let two remote clients ++ // be able to connect at the same time. ++ Thread connectionInitiatorThread = ++ new ConnectionInitiatorThread(connection); ++ connectionInitiatorThread.setName("Smack Link-local Connection Initiator"); ++ connectionInitiatorThread.setDaemon(true); ++ connectionInitiatorThread.start(); ++ } ++ catch (SocketException se) { ++ // If we are closing down, it's probably closed socket exception. ++ if (!done) { ++ throw new XMPPException("Link-local service unexpectedly closed down.", se); ++ } ++ } ++ catch (IOException ioe) { ++ throw new XMPPException("Link-local service unexpectedly closed down.", ioe); ++ } ++ } ++ } ++ ++ /** ++ * Bind one socket to any port within a given range. ++ * ++ * @param min the minimum port number allowed ++ * @param max hte maximum port number allowed ++ * @throws XMPPException if binding failed on all allowed ports. ++ */ ++ private static ServerSocket bindRange(int min, int max) throws XMPPException { ++ int port = 0; ++ for (int try_port = min; try_port <= max; try_port++) { ++ try { ++ ServerSocket socket = new ServerSocket(try_port); ++ return socket; ++ } ++ catch (IOException e) { ++ // failed to bind, try next ++ } ++ } ++ throw new XMPPException("Unable to bind port, no ports available."); ++ } ++ ++ protected void unknownOriginMessage(Message message) { ++ for (LLServiceStateListener listener : stateListeners) { ++ listener.unknownOriginMessage(message); ++ } ++ } ++ ++ protected void serviceNameChanged(String newName, String oldName) { ++ // update our own presence with the new name, for future connections ++ presence.setServiceName(newName); ++ ++ // Cleanup chats (remove tho two affected chats) ++ LLChat c1 = removeLLChat(newName); ++ LLChat c2 = removeLLChat(oldName); ++ for (LLChatListener listener : chatListeners) { ++ if (c1 != null) ++ listener.chatInvalidated(c1); ++ if (c2 != null) ++ listener.chatInvalidated(c1); ++ } ++ ++ // clean up connections ++ XMPPLLConnection c; ++ c = getConnectionTo(oldName); ++ if (c != null) ++ c.disconnect(); ++ c = getConnectionTo(newName); ++ if (c != null) ++ c.disconnect(); ++ ++ // notify listeners ++ for (LLServiceStateListener listener : stateListeners) { ++ listener.serviceNameChanged(newName, oldName); ++ } ++ } ++ ++ /** ++ * Adds a listener that are notified when a new link-local connection ++ * has been established. ++ * ++ * @param listener A class implementing the LLConnectionListener interface. ++ */ ++ public void addLLServiceConnectionListener(LLServiceConnectionListener listener) { ++ llServiceConnectionListeners.add(listener); ++ } ++ ++ /** ++ * Removes a listener from the new connection listener list. ++ * ++ * @param listener The class implementing the LLConnectionListener interface that ++ * is to be removed. ++ */ ++ public void removeLLServiceConnectionListener(LLServiceConnectionListener listener) { ++ llServiceConnectionListeners.remove(listener); ++ } ++ ++ private void notifyNewServiceConnection(XMPPLLConnection connection) { ++ for (LLServiceConnectionListener listener : llServiceConnectionListeners) { ++ listener.connectionCreated(connection); ++ } ++ } ++ ++ /** ++ * Add the given connection to the list of associated connections. ++ * An associated connection means it's a Link-Local connection managed ++ * by this service. ++ * ++ * @param connection the connection to be associated ++ */ ++ private void addAssociatedConnection(XMPPLLConnection connection) { ++ synchronized (associatedConnections) { ++ associatedConnections.add(connection); ++ } ++ } ++ ++ /** ++ * Remove the given connection from the list of associated connections. ++ * ++ * @param connection the connection to be removed. ++ */ ++ private void removeAssociatedConnection(XMPPLLConnection connection) { ++ synchronized (associatedConnections) { ++ associatedConnections.remove(connection); ++ } ++ } ++ ++ /** ++ * Return true if the given connection is associated / managed by this ++ * service. ++ * ++ * @param connection the connection to be checked ++ * @return true if the connection is associated with this service or false ++ * if it is not associated with this service. ++ */ ++ private boolean isAssociatedConnection(XMPPLLConnection connection) { ++ synchronized (associatedConnections) { ++ return associatedConnections.contains(connection); ++ } ++ } ++ ++ /** ++ * Add a packet listener. ++ * ++ * @param listener the PacketListener ++ * @param filter the Filter ++ */ ++ public void addPacketListener(PacketListener listener, PacketFilter filter) { ++ ListenerWrapper wrapper = new ListenerWrapper(listener, filter); ++ listeners.put(listener, wrapper); ++ ++ // Also add to existing connections ++ synchronized (ingoing) { ++ synchronized (outgoing) { ++ for (XMPPLLConnection c : getConnections()) { ++ c.addPacketListener(listener, filter); ++ } ++ } ++ } ++ } ++ ++ /** ++ * Remove a packet listener. ++ */ ++ public void removePacketListener(PacketListener listener) { ++ listeners.remove(listener); ++ ++ // Also add to existing connections ++ synchronized (ingoing) { ++ synchronized (outgoing) { ++ for (XMPPLLConnection c : getConnections()) { ++ c.removePacketListener(listener); ++ } ++ } ++ } ++ } ++ ++ /** ++ * Add service state listener. ++ * ++ * @param listener the service state listener to be added. ++ */ ++ public void addServiceStateListener(LLServiceStateListener listener) { ++ stateListeners.add(listener); ++ } ++ ++ /** ++ * Remove service state listener. ++ * ++ * @param listener the service state listener to be removed. ++ */ ++ public void removeServiceStateListener(LLServiceStateListener listener) { ++ stateListeners.remove(listener); ++ } ++ ++ /** ++ * Add Link-local chat session listener. The chat session listener will ++ * be notified when new link-local chat sessions are created. ++ * ++ * @param listener the listener to be added. ++ */ ++ public void addLLChatListener(LLChatListener listener) { ++ chatListeners.add(listener); ++ } ++ ++ /** ++ * Remove Link-local chat session listener. ++ * ++ * @param listener the listener to be removed. ++ */ ++ public void removeLLChatListener(LLChatListener listener) { ++ chatListeners.remove(listener); ++ } ++ ++ /** ++ * Add presence listener. A presence listener will be notified of new ++ * presences, presences going offline, and changes in presences. ++ * ++ * @param listener the listener to be added. ++ */ ++ public void addPresenceListener(LLPresenceListener listener) { ++ presenceDiscoverer.addPresenceListener(listener); ++ } ++ ++ /** ++ * Remove presence listener. ++ * ++ * @param listener presence listener to be removed. ++ */ ++ public void removePresenceListener(LLPresenceListener listener) { ++ presenceDiscoverer.removePresenceListener(listener); ++ } ++ ++ /** ++ * Get the presence information associated with the given service name. ++ * ++ * @param serviceName the service name which information should be returned. ++ * @return the service information. ++ */ ++ public LLPresence getPresenceByServiceName(String serviceName) { ++ return presenceDiscoverer.getPresence(serviceName); ++ } ++ ++ public CollectorWrapper createPacketCollector(PacketFilter filter) { ++ CollectorWrapper wrapper = new CollectorWrapper(filter); ++ collectorWrappers.add(wrapper); ++ return wrapper; ++ } ++ ++ /** ++ * Return a collection of all active connections. This may be used if the ++ * user wants to change a property on all connections, such as add a service ++ * discovery feature or other. ++ * ++ * @return a colllection of all active connections. ++ */ ++ public Collection getConnections() { ++ Collection connections = ++ new ArrayList(outgoing.values()); ++ connections.addAll(ingoing.values()); ++ return connections; ++ } ++ ++ /** ++ * Returns a connection to a given service name. ++ * First checks for an outgoing connection, if noone exists, ++ * try ingoing. ++ * ++ * @param serviceName the service name ++ * @return a connection associated with the service name or null if no ++ * connection is available. ++ */ ++ XMPPLLConnection getConnectionTo(String serviceName) { ++ XMPPLLConnection connection = outgoing.get(serviceName); ++ if (connection != null) ++ return connection; ++ return ingoing.get(serviceName); ++ } ++ ++ void addIngoingConnection(XMPPLLConnection connection) { ++ ingoing.put(connection.getServiceName(), connection); ++ } ++ ++ void removeIngoingConnection(XMPPLLConnection connection) { ++ ingoing.remove(connection.getServiceName()); ++ } ++ ++ void addOutgoingConnection(XMPPLLConnection connection) { ++ outgoing.put(connection.getServiceName(), connection); ++ } ++ ++ void removeOutgoingConnection(XMPPLLConnection connection) { ++ outgoing.remove(connection.getServiceName()); ++ } ++ ++ LLChat removeLLChat(String serviceName) { ++ return chats.remove(serviceName); ++ } ++ ++ void newLLChat(LLChat chat) { ++ chats.put(chat.getServiceName(), chat); ++ for (LLChatListener listener : chatListeners) { ++ listener.newChat(chat); ++ } ++ } ++ ++ /** ++ * Get a LLChat associated with a given service name. ++ * If no LLChat session is available, a new one is created. ++ * ++ * @param serviceName the service name ++ * @return a chat session instance associated with the given service name. ++ */ ++ public LLChat getChat(String serviceName) throws XMPPException { ++ LLChat chat = chats.get(serviceName); ++ if (chat == null) { ++ LLPresence presence = getPresenceByServiceName(serviceName); ++ if (presence == null) ++ throw new XMPPException("Can't initiate new chat to '" + ++ serviceName + "': mDNS presence unknown."); ++ chat = new LLChat(this, presence); ++ newLLChat(chat); ++ } ++ return chat; ++ } ++ ++ /** ++ * Returns a XMPPLLConnection to the serviceName. ++ * If no established connection exists, a new connection is created. ++ * ++ * @param serviceName Service name of the remote client. ++ * @return A connection to the given service name. ++ */ ++ public XMPPLLConnection getConnection(String serviceName) throws XMPPException { ++ // If a connection exists, return it. ++ XMPPLLConnection connection = getConnectionTo(serviceName); ++ if (connection != null) ++ return connection; ++ ++ // If no connection exists, look up the presence and connect according to. ++ LLPresence remotePresence = getPresenceByServiceName(serviceName); ++ ++ if (remotePresence == null) { ++ throw new XMPPException("Can't initiate connection, remote peer is not available."); ++ } ++ ++ LLConnectionConfiguration config = ++ new LLConnectionConfiguration(presence, remotePresence); ++ connection = new XMPPLLConnection(this, config); ++ // Associate the new connection with this service ++ addAssociatedConnection(connection); ++ connection.connect(); ++ addOutgoingConnection(connection); ++ ++ return connection; ++ } ++ ++ /** ++ * Send a message to the remote peer. ++ * ++ * @param message the message to be sent. ++ * @throws XMPPException if the message cannot be sent. ++ */ ++ void sendMessage(Message message) throws XMPPException { ++ sendPacket(message); ++ } ++ ++ ++ /** ++ * Send a packet to the remote peer. ++ * ++ * @param packet the packet to be sent. ++ * @throws XMPPException if the packet cannot be sent. ++ */ ++ public void sendPacket(Packet packet) throws XMPPException { ++ getConnection(packet.getTo()).sendPacket(packet); ++ } ++ ++ /** ++ * Send an IQ set or get and wait for the response. This function works ++ * different from a normal one-connection IQ request where a packet ++ * collector is created and added to the connection. This function ++ * takes care of (at least) two cases when this doesn't work: ++ *
        ++ *
      • Consider client A requests something from B. This is done by ++ * A connecting to B (no existing connection is available), then ++ * sending an IQ request to B using the new connection and starts ++ * waiting for a reply. However the connection between them may be ++ * terminated due to inactivity, and for B to reply, it have to ++ * establish a new connection. This function takes care of this ++ * by listening for the packets on all new connections.
      • ++ *
      • Consider client A and client B concurrently establishes ++ * connections between them. This will result in two parallell ++ * connections between the two entities and the two clients may ++ * choose whatever connection to use when communicating. This ++ * function takes care of the possibility that if A requests ++ * something from B using connection #1 and B replies using ++ * connection #2, the packet will still be collected.
      • ++ *
      ++ */ ++ public IQ getIQResponse(IQ request) throws XMPPException { ++ XMPPLLConnection connection = getConnection(request.getTo()); ++ ++ // Create a packet collector to listen for a response. ++ // Filter: req.id == rpl.id ^ (rp.iqtype in (result, error)) ++ CollectorWrapper collector = createPacketCollector( ++ new AndFilter( ++ new PacketIDFilter(request.getPacketID()), ++ new OrFilter( ++ new IQTypeFilter(IQ.Type.RESULT), ++ new IQTypeFilter(IQ.Type.ERROR)))); ++ ++ connection.sendPacket(request); ++ ++ // Wait up to 5 seconds for a result. ++ IQ result = (IQ) collector.nextResult( ++ SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Stop queuing results ++ collector.cancel(); ++ if (result == null) { ++ throw new XMPPException("No response from the remote host."); ++ } ++ ++ return result; ++ } ++ ++ /** ++ * Update the presence information announced by the mDNS/DNS-SD daemon. ++ * The presence object stored in the LLService class will be updated ++ * with the new information and the daemon will reannounce the changes. ++ * ++ * @param presence the new presence information ++ * @throws XMPPException if an error occurs ++ */ ++ public void updatePresence(LLPresence presence) throws XMPPException { ++ this.presence.update(presence); ++ ++ if (initiated) { ++ updateText(); ++ reannounceService(); ++ } ++ } ++ ++ /** ++ * Get current Link-local presence. ++ */ ++ public LLPresence getLocalPresence() { ++ return presence; ++ } ++ ++ /** ++ * ConnectionActivityListener listens for link-local connection activity ++ * such as closed connection and broken connection, and keeps record of ++ * what active connections exist up to date. ++ */ ++ private class ConnectionActivityListener implements ConnectionListener { ++ private XMPPLLConnection connection; ++ ++ ConnectionActivityListener(XMPPLLConnection connection) { ++ this.connection = connection; ++ } ++ ++ public void connectionClosed() { ++ removeConnectionRecord(); ++ } ++ ++ public void connectionClosedOnError(Exception e) { ++ removeConnectionRecord(); ++ } ++ ++ public void reconnectingIn(int seconds) { ++ } ++ ++ public void reconnectionSuccessful() { ++ } ++ ++ public void reconnectionFailed(Exception e) { ++ } ++ ++ private void removeConnectionRecord() { ++ if (connection.isInitiator()) ++ removeOutgoingConnection(connection); ++ else ++ removeIngoingConnection(connection); ++ ++ removeAssociatedConnection(connection); ++ } ++ } ++ ++ /** ++ * MessageListener listenes for messages from connections and delivers them ++ * to the corresponding chat session. If no session is available, a new one ++ * is created. ++ */ ++ private class MessageListener implements PacketListener { ++ public void processPacket(Packet packet) { ++ // handle message ++ if (packet instanceof Message) { ++ Message message = (Message)packet; ++ String remoteServiceName = message.getFrom(); ++ LLPresence presence = getPresenceByServiceName(remoteServiceName); ++ ++ // Get existing chat instance or create a new one and deliver ++ // the message. ++ try { ++ getChat(remoteServiceName).deliver(message); ++ } ++ catch (XMPPException xe) { ++ // If getChat throws an exception, it's because no ++ // presence could be found, thus known origin. ++ service.unknownOriginMessage(message); ++ } ++ } ++ } ++ } ++ ++ /** ++ * Initiates a connection in a seperate thread, controlling ++ * it was established correctly and stream was initiated. ++ */ ++ private class ConnectionInitiatorThread extends Thread { ++ XMPPLLConnection connection; ++ ++ ConnectionInitiatorThread(XMPPLLConnection connection) { ++ this.connection = connection; ++ } ++ ++ public void run() { ++ try { ++ connection.initListen(); ++ } ++ catch (XMPPException xe) { ++ // ignore, since its an incoming connection ++ // there is nothing to save ++ } ++ } ++ } ++ ++ /** ++ * A wrapper class to associate a packet filter with a listener. ++ */ ++ private static class ListenerWrapper { ++ ++ private PacketListener packetListener; ++ private PacketFilter packetFilter; ++ ++ public ListenerWrapper(PacketListener packetListener, PacketFilter packetFilter) { ++ this.packetListener = packetListener; ++ this.packetFilter = packetFilter; ++ } ++ ++ public void notifyListener(Packet packet) { ++ if (packetFilter == null || packetFilter.accept(packet)) { ++ packetListener.processPacket(packet); ++ } ++ } ++ ++ public PacketListener getPacketListener() { ++ return packetListener; ++ } ++ ++ public PacketFilter getPacketFilter() { ++ return packetFilter; ++ } ++ } ++ ++ /** ++ * Packet Collector Wrapper which is used for collecting packages ++ * from multiple connections as well as newly established connections (works ++ * together with LLService constructor. ++ */ ++ public class CollectorWrapper { ++ // Existing collectors. ++ private Set collectors = ++ new CopyOnWriteArraySet(); ++ ++ // Packet filter for all the collectors. ++ private PacketFilter packetFilter; ++ ++ // A common object used for shared locking between ++ // the collectors. ++ private Object lock = new Object(); ++ ++ private CollectorWrapper(PacketFilter packetFilter) { ++ this.packetFilter = packetFilter; ++ ++ // Apply to all active connections ++ for (XMPPLLConnection connection : getConnections()) { ++ createPacketCollector(connection); ++ } ++ } ++ ++ /** ++ * Create a new per-connection packet collector. ++ * ++ * @param connection the connection the collector should be added to. ++ */ ++ private void createPacketCollector(XMPPLLConnection connection) { ++ synchronized (connection) { ++ PacketCollector collector = ++ connection.createPacketCollector(packetFilter); ++ collector.setLock(lock); ++ collectors.add(collector); ++ } ++ } ++ ++ /** ++ * Returns the next available packet. The method call will block (not return) ++ * until a packet is available or the timeout has elapased. If the ++ * timeout elapses without a result, null will be returned. ++ * ++ * @param timeout the amount of time to wait for the next packet ++ * (in milleseconds). ++ * @return the next available packet. ++ */ ++ public synchronized Packet nextResult(long timeout) { ++ Packet packet; ++ long waitTime = timeout; ++ long start = System.currentTimeMillis(); ++ ++ try { ++ while (true) { ++ for (PacketCollector c : collectors) { ++ if (c.isCanceled()) ++ collectors.remove(c); ++ else { ++ packet = c.pollResult(); ++ if (packet != null) ++ return packet; ++ } ++ } ++ ++ if (waitTime <= 0) { ++ break; ++ } ++ ++ // wait ++ synchronized (lock) { ++ lock.wait(waitTime); ++ } ++ long now = System.currentTimeMillis(); ++ waitTime -= (now - start); ++ } ++ } ++ catch (InterruptedException ie) { ++ // ignore ++ } ++ ++ return null; ++ } ++ ++ public void cancel() { ++ for (PacketCollector c : collectors) { ++ c.cancel(); ++ } ++ collectorWrappers.remove(this); ++ } ++ } ++} ++ +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLServiceConnectionListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLServiceConnectionListener.java +new file mode 100644 +index 0000000..7d50d87 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLServiceConnectionListener.java +@@ -0,0 +1,15 @@ ++package org.jivesoftware.smack; ++ ++/** ++ * Notification about when new Link-local connections associated with a ++ * specific Link-local service has been established. ++ */ ++public interface LLServiceConnectionListener { ++ ++ /** ++ * A new link-local connection has been established. ++ * ++ * @param connection the new established connection. ++ */ ++ public void connectionCreated(XMPPLLConnection connection); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLServiceListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLServiceListener.java +new file mode 100644 +index 0000000..febaf36 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLServiceListener.java +@@ -0,0 +1,14 @@ ++package org.jivesoftware.smack; ++ ++/** ++ * Notification for new Link-local services created. ++ */ ++public interface LLServiceListener { ++ ++ /** ++ * The function called when a new Link-local service is created. ++ * ++ * @param service the new service ++ */ ++ public void serviceCreated(LLService service); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLServiceStateListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLServiceStateListener.java +new file mode 100644 +index 0000000..936fbc0 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLServiceStateListener.java +@@ -0,0 +1,39 @@ ++package org.jivesoftware.smack; ++ ++import org.jivesoftware.smack.packet.Message; ++ ++/** ++ * Interface for handeling link-local service events such as ++ * service closing, service crashes and other events. ++ */ ++public interface LLServiceStateListener { ++ ++ /** ++ * Notification that the service name was changed. ++ * ++ * @param newName the new service name ++ * @param oldName the previous service name ++ */ ++ public void serviceNameChanged(String newName, String oldName); ++ ++ /** ++ * Notification that the connection was closed normally. ++ */ ++ public void serviceClosed(); ++ ++ /** ++ * Notification that the connection was closed due to an exception. ++ * ++ * @param e the exception. ++ */ ++ public void serviceClosedOnError(Exception e); ++ ++ /** ++ * Notification that a message with unknown presence was received. ++ * This could be someone being invisible, meaning no presece is ++ * announced. ++ * ++ * @param e the exception. ++ */ ++ public void unknownOriginMessage(Message e); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/MessageListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/MessageListener.java +new file mode 100644 +index 0000000..187c56c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/MessageListener.java +@@ -0,0 +1,30 @@ ++/** ++ * $RCSfile$ ++ * $Revision: 2407 $ ++ * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.packet.Message; ++ ++/** ++ * ++ */ ++public interface MessageListener { ++ void processMessage(Chat chat, Message message); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/NonSASLAuthentication.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/NonSASLAuthentication.java +new file mode 100644 +index 0000000..c1fe12d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/NonSASLAuthentication.java +@@ -0,0 +1,143 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.packet.Authentication; ++import org.jivesoftware.smack.packet.IQ; ++ ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++import org.apache.harmony.javax.security.auth.callback.PasswordCallback; ++import org.apache.harmony.javax.security.auth.callback.Callback; ++ ++/** ++ * Implementation of JEP-0078: Non-SASL Authentication. Follow the following ++ * link to obtain more ++ * information about the JEP. ++ * ++ * @author Gaston Dombiak ++ */ ++class NonSASLAuthentication implements UserAuthentication { ++ ++ private Connection connection; ++ ++ public NonSASLAuthentication(Connection connection) { ++ super(); ++ this.connection = connection; ++ } ++ ++ public String authenticate(String username, String resource, CallbackHandler cbh) throws XMPPException { ++ //Use the callback handler to determine the password, and continue on. ++ PasswordCallback pcb = new PasswordCallback("Password: ",false); ++ try { ++ cbh.handle(new Callback[]{pcb}); ++ return authenticate(username, String.valueOf(pcb.getPassword()),resource); ++ } catch (Exception e) { ++ throw new XMPPException("Unable to determine password.",e); ++ } ++ } ++ ++ public String authenticate(String username, String password, String resource) throws ++ XMPPException { ++ // If we send an authentication packet in "get" mode with just the username, ++ // the server will return the list of authentication protocols it supports. ++ Authentication discoveryAuth = new Authentication(); ++ discoveryAuth.setType(IQ.Type.GET); ++ discoveryAuth.setUsername(username); ++ ++ PacketCollector collector = ++ connection.createPacketCollector(new PacketIDFilter(discoveryAuth.getPacketID())); ++ // Send the packet ++ connection.sendPacket(discoveryAuth); ++ // Wait up to a certain number of seconds for a response from the server. ++ IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ if (response == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ // If the server replied with an error, throw an exception. ++ else if (response.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(response.getError()); ++ } ++ // Otherwise, no error so continue processing. ++ Authentication authTypes = (Authentication) response; ++ collector.cancel(); ++ ++ // Now, create the authentication packet we'll send to the server. ++ Authentication auth = new Authentication(); ++ auth.setUsername(username); ++ ++ // Figure out if we should use digest or plain text authentication. ++ if (authTypes.getDigest() != null) { ++ auth.setDigest(connection.getConnectionID(), password); ++ } ++ else if (authTypes.getPassword() != null) { ++ auth.setPassword(password); ++ } ++ else { ++ throw new XMPPException("Server does not support compatible authentication mechanism."); ++ } ++ ++ auth.setResource(resource); ++ ++ collector = connection.createPacketCollector(new PacketIDFilter(auth.getPacketID())); ++ // Send the packet. ++ connection.sendPacket(auth); ++ // Wait up to a certain number of seconds for a response from the server. ++ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ if (response == null) { ++ throw new XMPPException("Authentication failed."); ++ } ++ else if (response.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(response.getError()); ++ } ++ // We're done with the collector, so explicitly cancel it. ++ collector.cancel(); ++ ++ return response.getTo(); ++ } ++ ++ public String authenticateAnonymously() throws XMPPException { ++ // Create the authentication packet we'll send to the server. ++ Authentication auth = new Authentication(); ++ ++ PacketCollector collector = ++ connection.createPacketCollector(new PacketIDFilter(auth.getPacketID())); ++ // Send the packet. ++ connection.sendPacket(auth); ++ // Wait up to a certain number of seconds for a response from the server. ++ IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ if (response == null) { ++ throw new XMPPException("Anonymous login failed."); ++ } ++ else if (response.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(response.getError()); ++ } ++ // We're done with the collector, so explicitly cancel it. ++ collector.cancel(); ++ ++ if (response.getTo() != null) { ++ return response.getTo(); ++ } ++ else { ++ return connection.getServiceName() + "/" + ((Authentication) response).getResource(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/OpenTrustManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/OpenTrustManager.java +new file mode 100644 +index 0000000..223bec6 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/OpenTrustManager.java +@@ -0,0 +1,49 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 javax.net.ssl.X509TrustManager; ++import java.security.cert.CertificateException; ++import java.security.cert.X509Certificate; ++ ++/** ++ * Dummy trust manager that trust all certificates presented by the server. This class ++ * is used during old SSL connections. ++ * ++ * @author Gaston Dombiak ++ */ ++class OpenTrustManager implements X509TrustManager { ++ ++ public OpenTrustManager() { ++ } ++ ++ public X509Certificate[] getAcceptedIssuers() { ++ return new X509Certificate[0]; ++ } ++ ++ public void checkClientTrusted(X509Certificate[] arg0, String arg1) ++ throws CertificateException { ++ } ++ ++ public void checkServerTrusted(X509Certificate[] arg0, String arg1) ++ throws CertificateException { ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketCollector.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketCollector.java +new file mode 100644 +index 0000000..d07f8eb +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketCollector.java +@@ -0,0 +1,239 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.packet.Packet; ++ ++import java.util.LinkedList; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.LinkedBlockingQueue; ++ ++/** ++ * Provides a mechanism to collect packets into a result queue that pass a ++ * specified filter. The collector lets you perform blocking and polling ++ * operations on the result queue. So, a PacketCollector is more suitable to ++ * use than a {@link PacketListener} when you need to wait for a specific ++ * result.

      ++ * ++ * Each packet collector will queue up a configured number of packets for processing before ++ * older packets are automatically dropped. The default number is retrieved by ++ * {@link SmackConfiguration#getPacketCollectorSize()}. ++ * ++ * @see Connection#createPacketCollector(PacketFilter) ++ * @author Matt Tucker ++ */ ++public class PacketCollector { ++ ++ /** ++ * Max number of packets that any one collector can hold. After the max is ++ * reached, older packets will be automatically dropped from the queue as ++ * new packets are added. ++ */ ++ private int maxPackets = SmackConfiguration.getPacketCollectorSize(); ++ ++ private PacketFilter packetFilter; ++ private LinkedList resultQueue; ++ private Connection conection; ++ private boolean cancelled = false; ++ private Object lock = new Object(); ++ ++ /** ++ * Creates a new packet collector. If the packet filter is null, then ++ * all packets will match this collector. ++ * ++ * @param conection the connection the collector is tied to. ++ * @param packetFilter determines which packets will be returned by this collector. ++ */ ++ protected PacketCollector(Connection conection, PacketFilter packetFilter) { ++ this.conection = conection; ++ this.packetFilter = packetFilter; ++ this.resultQueue = new LinkedList(); ++ } ++ ++ /** ++ * Set the lock used for notifying about new packages. ++ * ++ * @param lock the new lock object. ++ */ ++ public synchronized void setLock(Object lock) { ++ Object oldLock = this.lock; ++ this.lock = lock; ++ ++ // release the threads waiting for a notify on the old lock ++ synchronized (oldLock) { ++ oldLock.notifyAll(); ++ } ++ } ++ ++ /** ++ * Creates a new packet collector. If the packet filter is null, then ++ * all packets will match this collector. ++ * ++ * @param conection the connection the collector is tied to. ++ * @param packetFilter determines which packets will be returned by this collector. ++ * @param maxSize the maximum number of packets that will be stored in the collector. ++ */ ++ protected PacketCollector(Connection conection, PacketFilter packetFilter, int maxSize) { ++ this(conection, packetFilter); ++ maxPackets = maxSize; ++ } ++ ++ /** ++ * Explicitly cancels the packet collector so that no more results are ++ * queued up. Once a packet collector has been cancelled, it cannot be ++ * re-enabled. Instead, a new packet collector must be created. ++ */ ++ public void cancel() { ++ // If the packet collector has already been cancelled, do nothing. ++ if (!cancelled) { ++ cancelled = true; ++ conection.removePacketCollector(this); ++ } ++ } ++ ++ /** ++ * Returns true if the packet collector is canceled. ++ * ++ * @return true if canceled, false if still active. ++ */ ++ public boolean isCanceled() { ++ return cancelled; ++ } ++ ++ /** ++ * Returns the packet filter associated with this packet collector. The packet ++ * filter is used to determine what packets are queued as results. ++ * ++ * @return the packet filter. ++ */ ++ public PacketFilter getPacketFilter() { ++ return packetFilter; ++ } ++ ++ /** ++ * Polls to see if a packet is currently available and returns it, or ++ * immediately returns null if no packets are currently in the ++ * result queue. ++ * ++ * @return the next packet result, or null if there are no more ++ * results. ++ */ ++ public synchronized Packet pollResult() { ++ if (resultQueue.isEmpty()) { ++ return null; ++ } ++ else { ++ return resultQueue.removeLast(); ++ } ++ } ++ ++ /** ++ * Returns the next available packet. The method call will block (not return) ++ * until a packet is available. ++ * ++ * @return the next available packet. ++ */ ++ public Packet nextResult() { ++ // Wait indefinitely until there is a result to return. ++ while (resultQueue.isEmpty()) { ++ try { ++ synchronized (lock) { ++ lock.wait(); ++ } ++ } ++ catch (InterruptedException ie) { ++ // Ignore. ++ } ++ } ++ return resultQueue.removeLast(); ++ } ++ ++ /** ++ * Returns the next available packet. The method call will block (not return) ++ * until a packet is available or the timeout has elapased. If the ++ * timeout elapses without a result, null will be returned. ++ * ++ * @param timeout the amount of time to wait for the next packet (in milleseconds). ++ * @return the next available packet. ++ */ ++ public Packet nextResult(long timeout) { ++ // Wait up to the specified amount of time for a result. ++ if (resultQueue.isEmpty()) { ++ long waitTime = timeout; ++ long start = System.currentTimeMillis(); ++ try { ++ // Keep waiting until the specified amount of time has elapsed, or ++ // a packet is available to return. ++ while (resultQueue.isEmpty()) { ++ if (waitTime <= 0) { ++ break; ++ } ++ synchronized (lock) { ++ lock.wait(waitTime); ++ } ++ long now = System.currentTimeMillis(); ++ waitTime -= (now - start); ++ start = now; ++ } ++ } ++ catch (InterruptedException ie) { ++ // Ignore. ++ } ++ // Still haven't found a result, so return null. ++ if (resultQueue.isEmpty()) { ++ return null; ++ } ++ // Return the packet that was found. ++ else { ++ return resultQueue.removeLast(); ++ } ++ } ++ // There's already a packet waiting, so return it. ++ else { ++ return resultQueue.removeLast(); ++ } ++ } ++ ++ /** ++ * Processes a packet to see if it meets the criteria for this packet collector. ++ * If so, the packet is added to the result queue. ++ * ++ * @param packet the packet to process. ++ */ ++ protected synchronized void processPacket(Packet packet) { ++ if (packet == null) { ++ return; ++ } ++ if (packetFilter == null || packetFilter.accept(packet)) { ++ // If the max number of packets has been reached, remove the oldest one. ++ if (resultQueue.size() == maxPackets) { ++ resultQueue.removeLast(); ++ } ++ // Add the new packet. ++ resultQueue.addFirst(packet); ++ // Notify waiting threads a result is available. ++ synchronized (lock) { ++ lock.notifyAll(); ++ } ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketInterceptor.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketInterceptor.java +new file mode 100644 +index 0000000..bd89031 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketInterceptor.java +@@ -0,0 +1,49 @@ ++/** ++ * $Revision: 2408 $ ++ * $Date: 2004-11-02 20:53:30 -0300 (Tue, 02 Nov 2004) $ ++ * ++ * Copyright 2003-2005 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.packet.Packet; ++ ++/** ++ * Provides a mechanism to intercept and modify packets that are going to be ++ * sent to the server. PacketInterceptors are added to the {@link Connection} ++ * together with a {@link org.jivesoftware.smack.filter.PacketFilter} so that only ++ * certain packets are intercepted and processed by the interceptor.

      ++ * ++ * This allows event-style programming -- every time a new packet is found, ++ * the {@link #interceptPacket(Packet)} method will be called. ++ * ++ * @see Connection#addPacketInterceptor(PacketInterceptor, org.jivesoftware.smack.filter.PacketFilter) ++ * @author Gaston Dombiak ++ */ ++public interface PacketInterceptor { ++ ++ /** ++ * Process the packet that is about to be sent to the server. The intercepted ++ * packet can be modified by the interceptor.

      ++ * ++ * Interceptors are invoked using the same thread that requested the packet ++ * to be sent, so it's very important that implementations of this method ++ * not block for any extended period of time. ++ * ++ * @param packet the packet to is going to be sent to the server. ++ */ ++ public void interceptPacket(Packet packet); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketListener.java +new file mode 100644 +index 0000000..4bc83aa +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketListener.java +@@ -0,0 +1,48 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.packet.Packet; ++ ++/** ++ * Provides a mechanism to listen for packets that pass a specified filter. ++ * This allows event-style programming -- every time a new packet is found, ++ * the {@link #processPacket(Packet)} method will be called. This is the ++ * opposite approach to the functionality provided by a {@link PacketCollector} ++ * which lets you block while waiting for results. ++ * ++ * @see Connection#addPacketListener(PacketListener, org.jivesoftware.smack.filter.PacketFilter) ++ * @author Matt Tucker ++ */ ++public interface PacketListener { ++ ++ /** ++ * Process the next packet sent to this packet listener.

      ++ * ++ * A single thread is responsible for invoking all listeners, so ++ * it's very important that implementations of this method not block ++ * for any extended period of time. ++ * ++ * @param packet the packet to process. ++ */ ++ public void processPacket(Packet packet); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketReader.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketReader.java +new file mode 100644 +index 0000000..ba79d40 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketReader.java +@@ -0,0 +1,493 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.Connection.ListenerWrapper; ++import org.jivesoftware.smack.packet.*; ++import org.jivesoftware.smack.sasl.SASLMechanism.Challenge; ++import org.jivesoftware.smack.sasl.SASLMechanism.Failure; ++import org.jivesoftware.smack.sasl.SASLMechanism.Success; ++import org.jivesoftware.smack.util.PacketParserUtils; ++import org.xmlpull.v1.XmlPullParserFactory; ++import org.xmlpull.v1.XmlPullParser; ++import org.xmlpull.v1.XmlPullParserException; ++ ++import java.util.concurrent.*; ++ ++/** ++ * Listens for XML traffic from the XMPP server and parses it into packet objects. ++ * The packet reader also invokes all packet listeners and collectors.

      ++ * ++ * @see Connection#createPacketCollector ++ * @see Connection#addPacketListener ++ * @author Matt Tucker ++ */ ++class PacketReader { ++ ++ protected Thread readerThread; ++ private ExecutorService listenerExecutor; ++ ++ private XMPPConnection connection; ++ protected XmlPullParser parser; ++ protected boolean done; ++ ++ protected String connectionID = null; ++ private Semaphore connectionSemaphore; ++ ++ protected PacketReader(final XMPPConnection connection) { ++ this.connection = connection; ++ this.init(); ++ } ++ ++ /** ++ * Initializes the reader in order to be used. The reader is initialized during the ++ * first connection and when reconnecting due to an abruptly disconnection. ++ */ ++ protected void init() { ++ done = false; ++ connectionID = null; ++ ++ readerThread = new Thread() { ++ public void run() { ++ parsePackets(this); ++ } ++ }; ++ readerThread.setName("Smack Packet Reader (" + connection.connectionCounterValue + ")"); ++ readerThread.setDaemon(true); ++ ++ // Create an executor to deliver incoming packets to listeners. We'll use a single ++ // thread with an unbounded queue. ++ listenerExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() { ++ ++ public Thread newThread(Runnable runnable) { ++ Thread thread = new Thread(runnable, ++ "Smack Listener Processor (" + connection.connectionCounterValue + ")"); ++ thread.setDaemon(true); ++ return thread; ++ } ++ }); ++ ++ resetParser(); ++ } ++ ++ /** ++ * Starts the packet reader thread and returns once a connection to the server ++ * has been established. A connection will be attempted for a maximum of five ++ * seconds. An XMPPException will be thrown if the connection fails. ++ * ++ * @throws XMPPException if the server fails to send an opening stream back ++ * for more than five seconds. ++ */ ++ public void startup() throws XMPPException { ++ connectionSemaphore = new Semaphore(1); ++ ++ readerThread.start(); ++ // Wait for stream tag before returing. We'll wait a couple of seconds before ++ // giving up and throwing an error. ++ try { ++ connectionSemaphore.acquire(); ++ ++ // A waiting thread may be woken up before the wait time or a notify ++ // (although this is a rare thing). Therefore, we continue waiting ++ // until either a connectionID has been set (and hence a notify was ++ // made) or the total wait time has elapsed. ++ int waitTime = SmackConfiguration.getPacketReplyTimeout(); ++ connectionSemaphore.tryAcquire(3 * waitTime, TimeUnit.MILLISECONDS); ++ } ++ catch (InterruptedException ie) { ++ // Ignore. ++ } ++ if (connectionID == null) { ++ throw new XMPPException("Connection failed. No response from server."); ++ } ++ else { ++ connection.connectionID = connectionID; ++ } ++ } ++ ++ /** ++ * Shuts the packet reader down. ++ */ ++ public void shutdown() { ++ // Notify connection listeners of the connection closing if done hasn't already been set. ++ if (!done) { ++ for (ConnectionListener listener : connection.getConnectionListeners()) { ++ try { ++ listener.connectionClosed(); ++ } ++ catch (Exception e) { ++ // Catch and print any exception so we can recover ++ // from a faulty listener and finish the shutdown process ++ e.printStackTrace(); ++ } ++ } ++ } ++ done = true; ++ ++ // Shut down the listener executor. ++ listenerExecutor.shutdown(); ++ } ++ ++ /** ++ * Cleans up all resources used by the packet reader. ++ */ ++ void cleanup() { ++ connection.recvListeners.clear(); ++ connection.collectors.clear(); ++ } ++ ++ /** ++ * Sends out a notification that there was an error with the connection ++ * and closes the connection. ++ * ++ * @param e the exception that causes the connection close event. ++ */ ++ void notifyConnectionError(Exception e) { ++ done = true; ++ // Closes the connection temporary. A reconnection is possible ++ connection.quickShutdown(); ++ // Print the stack trace to help catch the problem ++ e.printStackTrace(); ++ // Notify connection listeners of the error. ++ for (ConnectionListener listener : connection.getConnectionListeners()) { ++ try { ++ listener.connectionClosedOnError(e); ++ } ++ catch (Exception e2) { ++ // Catch and print any exception so we can recover ++ // from a faulty listener ++ e2.printStackTrace(); ++ } ++ } ++ } ++ ++ /** ++ * Sends a notification indicating that the connection was reconnected successfully. ++ */ ++ protected void notifyReconnection() { ++ // Notify connection listeners of the reconnection. ++ for (ConnectionListener listener : connection.getConnectionListeners()) { ++ try { ++ listener.reconnectionSuccessful(); ++ } ++ catch (Exception e) { ++ // Catch and print any exception so we can recover ++ // from a faulty listener ++ e.printStackTrace(); ++ } ++ } ++ } ++ ++ /** ++ * Resets the parser using the latest connection's reader. Reseting the parser is necessary ++ * when the plain connection has been secured or when a new opening stream element is going ++ * to be sent by the server. ++ */ ++ private void resetParser() { ++ try { ++ parser = XmlPullParserFactory.newInstance().newPullParser(); ++ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); ++ parser.setInput(connection.reader); ++ } ++ catch (XmlPullParserException xppe) { ++ xppe.printStackTrace(); ++ } ++ } ++ ++ /** ++ * Parse top-level packets in order to process them further. ++ * ++ * @param thread the thread that is being used by the reader to parse incoming packets. ++ */ ++ protected void parsePackets(Thread thread) { ++ try { ++ int eventType = parser.getEventType(); ++ do { ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("message")) { ++ processPacket(PacketParserUtils.parseMessage(parser)); ++ } ++ else if (parser.getName().equals("iq")) { ++ processPacket(PacketParserUtils.parseIQ(parser, connection)); ++ } ++ else if (parser.getName().equals("presence")) { ++ processPacket(PacketParserUtils.parsePresence(parser)); ++ } ++ // We found an opening stream. Record information about it, then notify ++ // the connectionID lock so that the packet reader startup can finish. ++ else if (parser.getName().equals("stream")) { ++ // Ensure the correct jabber:client namespace is being used. ++ if ("jabber:client".equals(parser.getNamespace(null))) { ++ // Get the connection id. ++ for (int i=0; i ++ * ++ * 1) An opening stream was sent from a non XMPP 1.0 compliant server ++ * 2) Stream features were received from an XMPP 1.0 compliant server that does not support TLS ++ * 3) TLS negotiation was successful ++ * ++ */ ++ protected void releaseConnectionIDLock() { ++ connectionSemaphore.release(); ++ } ++ ++ /** ++ * Processes a packet after it's been fully parsed by looping through the installed ++ * packet collectors and listeners and letting them examine the packet to see if ++ * they are a match with the filter. ++ * ++ * @param packet the packet to process. ++ */ ++ protected void processPacket(Packet packet) { ++ if (packet == null) { ++ return; ++ } ++ ++ // Loop through all collectors and notify the appropriate ones. ++ for (PacketCollector collector: connection.getPacketCollectors()) { ++ collector.processPacket(packet); ++ } ++ ++ // Deliver the incoming packet to listeners. ++ listenerExecutor.submit(new ListenerNotification(packet)); ++ } ++ ++ private void parseFeatures(XmlPullParser parser) throws Exception { ++ boolean startTLSReceived = false; ++ boolean startTLSRequired = false; ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("starttls")) { ++ startTLSReceived = true; ++ } ++ else if (parser.getName().equals("mechanisms")) { ++ // The server is reporting available SASL mechanisms. Store this information ++ // which will be used later while logging (i.e. authenticating) into ++ // the server ++ connection.getSASLAuthentication() ++ .setAvailableSASLMethods(PacketParserUtils.parseMechanisms(parser)); ++ } ++ else if (parser.getName().equals("bind")) { ++ // The server requires the client to bind a resource to the stream ++ connection.getSASLAuthentication().bindingRequired(); ++ } ++ else if(parser.getName().equals("ver")){ ++ connection.getConfiguration().setRosterVersioningAvailable(true); ++ } ++ else if(parser.getName().equals("c")){ ++ String node = parser.getAttributeValue(null, "node"); ++ String ver = parser.getAttributeValue(null, "ver"); ++ String capsNode = node+"#"+ver; ++ connection.getConfiguration().setCapsNode(capsNode); ++ } ++ else if (parser.getName().equals("session")) { ++ // The server supports sessions ++ connection.getSASLAuthentication().sessionsSupported(); ++ } ++ else if (parser.getName().equals("compression")) { ++ // The server supports stream compression ++ connection.setAvailableCompressionMethods(PacketParserUtils.parseCompressionMethods(parser)); ++ } ++ else if (parser.getName().equals("register")) { ++ connection.getAccountManager().setSupportsAccountCreation(true); ++ } ++ else { ++ try { ++ UnknownPacket packet = (UnknownPacket) PacketParserUtils.parsePacketExtension(parser.getName(), ++ parser.getNamespace(), parser); ++ processPacket(packet); ++ } ++ catch (ClassCastException ex) { ++ // ignore ++ } ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("starttls")) { ++ // Confirm the server that we want to use TLS ++ connection.startTLSReceived(startTLSRequired); ++ } ++ else if (parser.getName().equals("required") && startTLSReceived) { ++ startTLSRequired = true; ++ } ++ else if (parser.getName().equals("features")) { ++ done = true; ++ } ++ } ++ } ++ ++ // If TLS is required but the server doesn't offer it, disconnect ++ // from the server and throw an error. First check if we've already negotiated TLS ++ // and are secure, however (features get parsed a second time after TLS is established). ++ if (!connection.isSecureConnection()) { ++ if (!startTLSReceived && connection.getConfiguration().getSecurityMode() == ++ ConnectionConfiguration.SecurityMode.required) ++ { ++ throw new XMPPException("Server does not support security (TLS), " + ++ "but security required by connection configuration.", ++ new XMPPError(XMPPError.Condition.forbidden)); ++ } ++ } ++ ++ // Release the lock after TLS has been negotiated or we are not insterested in TLS ++ if (!startTLSReceived || connection.getConfiguration().getSecurityMode() == ++ ConnectionConfiguration.SecurityMode.disabled) ++ { ++ releaseConnectionIDLock(); ++ } ++ } ++ ++ /** ++ * A runnable to notify all listeners of a packet. ++ */ ++ private class ListenerNotification implements Runnable { ++ ++ private Packet packet; ++ ++ public ListenerNotification(Packet packet) { ++ this.packet = packet; ++ } ++ ++ public void run() { ++ for (ListenerWrapper listenerWrapper : connection.recvListeners.values()) { ++ listenerWrapper.notifyListener(packet); ++ } ++ } ++ } ++ ++ public void quickShutdown() { ++ done = true; ++ ++ // Shut down the listener executor. ++ listenerExecutor.shutdown(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketWriter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketWriter.java +new file mode 100644 +index 0000000..d0721f8 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketWriter.java +@@ -0,0 +1,320 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.packet.Packet; ++ ++import java.io.IOException; ++import java.io.Writer; ++import java.util.concurrent.ArrayBlockingQueue; ++import java.util.concurrent.BlockingQueue; ++ ++/** ++ * Writes packets to a XMPP server. Packets are sent using a dedicated thread. Packet ++ * interceptors can be registered to dynamically modify packets before they're actually ++ * sent. Packet listeners can be registered to listen for all outgoing packets. ++ * ++ * @see Connection#addPacketInterceptor ++ * @see Connection#addPacketSendingListener ++ * ++ * @author Matt Tucker ++ */ ++class PacketWriter { ++ ++ private Thread writerThread; ++ private Thread keepAliveThread; ++ private Writer writer; ++ private XMPPConnection connection; ++ private final BlockingQueue queue; ++ private boolean done; ++ ++ /** ++ * Timestamp when the last stanza was sent to the server. This information is used ++ * by the keep alive process to only send heartbeats when the connection has been idle. ++ */ ++ private long lastActive = System.currentTimeMillis(); ++ ++ /** ++ * Creates a new packet writer with the specified connection. ++ * ++ * @param connection the connection. ++ */ ++ protected PacketWriter(XMPPConnection connection) { ++ this.queue = new ArrayBlockingQueue(500, true); ++ this.connection = connection; ++ init(); ++ } ++ ++ /** ++ * Initializes the writer in order to be used. It is called at the first connection and also ++ * is invoked if the connection is disconnected by an error. ++ */ ++ protected void init() { ++ this.writer = connection.writer; ++ done = false; ++ ++ writerThread = new Thread() { ++ public void run() { ++ writePackets(this); ++ } ++ }; ++ writerThread.setName("Smack Packet Writer (" + connection.connectionCounterValue + ")"); ++ writerThread.setDaemon(true); ++ } ++ ++ /** ++ * Sends the specified packet to the server. ++ * ++ * @param packet the packet to send. ++ */ ++ public void sendPacket(Packet packet) { ++ if (!done) { ++ // Invoke interceptors for the new packet that is about to be sent. Interceptors ++ // may modify the content of the packet. ++ connection.firePacketInterceptors(packet); ++ ++ try { ++ queue.put(packet); ++ } ++ catch (InterruptedException ie) { ++ ie.printStackTrace(); ++ return; ++ } ++ synchronized (queue) { ++ queue.notifyAll(); ++ } ++ ++ // Process packet writer listeners. Note that we're using the sending ++ // thread so it's expected that listeners are fast. ++ connection.firePacketSendingListeners(packet); ++ } ++ } ++ ++ /** ++ * Starts the packet writer thread and opens a connection to the server. The ++ * packet writer will continue writing packets until {@link #shutdown} or an ++ * error occurs. ++ */ ++ public void startup() { ++ writerThread.start(); ++ } ++ ++ /** ++ * Starts the keep alive process. A white space (aka heartbeat) is going to be ++ * sent to the server every 30 seconds (by default) since the last stanza was sent ++ * to the server. ++ */ ++ void startKeepAliveProcess() { ++ // Schedule a keep-alive task to run if the feature is enabled. will write ++ // out a space character each time it runs to keep the TCP/IP connection open. ++ int keepAliveInterval = SmackConfiguration.getKeepAliveInterval(); ++ if (keepAliveInterval > 0) { ++ KeepAliveTask task = new KeepAliveTask(keepAliveInterval); ++ keepAliveThread = new Thread(task); ++ task.setThread(keepAliveThread); ++ keepAliveThread.setDaemon(true); ++ keepAliveThread.setName("Smack Keep Alive (" + connection.connectionCounterValue + ")"); ++ keepAliveThread.start(); ++ } ++ } ++ ++ void setWriter(Writer writer) { ++ this.writer = writer; ++ } ++ ++ /** ++ * Shuts down the packet writer. Once this method has been called, no further ++ * packets will be written to the server. ++ */ ++ public void shutdown() { ++ done = true; ++ synchronized (queue) { ++ queue.notifyAll(); ++ } ++ // Interrupt the keep alive thread if one was created ++ if (keepAliveThread != null) ++ keepAliveThread.interrupt(); ++ } ++ ++ /** ++ * Cleans up all resources used by the packet writer. ++ */ ++ void cleanup() { ++ connection.interceptors.clear(); ++ connection.sendListeners.clear(); ++ } ++ ++ /** ++ * Returns the next available packet from the queue for writing. ++ * ++ * @return the next packet for writing. ++ */ ++ private Packet nextPacket() { ++ Packet packet = null; ++ // Wait until there's a packet or we're done. ++ while (!done && (packet = queue.poll()) == null) { ++ try { ++ synchronized (queue) { ++ queue.wait(); ++ } ++ } ++ catch (InterruptedException ie) { ++ // Do nothing ++ } ++ } ++ return packet; ++ } ++ ++ private void writePackets(Thread thisThread) { ++ try { ++ // Open the stream. ++ openStream(); ++ // Write out packets from the queue. ++ while (!done && (writerThread == thisThread)) { ++ Packet packet = nextPacket(); ++ if (packet != null) { ++ synchronized (writer) { ++ writer.write(packet.toXML()); ++ writer.flush(); ++ // Keep track of the last time a stanza was sent to the server ++ lastActive = System.currentTimeMillis(); ++ } ++ } ++ } ++ // Flush out the rest of the queue. If the queue is extremely large, it's possible ++ // we won't have time to entirely flush it before the socket is forced closed ++ // by the shutdown process. ++ try { ++ synchronized (writer) { ++ while (!queue.isEmpty()) { ++ Packet packet = queue.remove(); ++ writer.write(packet.toXML()); ++ } ++ writer.flush(); ++ } ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ } ++ ++ // Delete the queue contents (hopefully nothing is left). ++ queue.clear(); ++ ++ // Close the stream. ++ try { ++ writer.write(""); ++ writer.flush(); ++ } ++ catch (Exception e) { ++ // Do nothing ++ } ++ finally { ++ try { ++ writer.close(); ++ } ++ catch (Exception e) { ++ // Do nothing ++ } ++ } ++ } ++ catch (IOException ioe){ ++ if (!done && !connection.isSocketClosed()) { ++ done = true; ++ // packetReader could be set to null by an concurrent disconnect() call. ++ // Therefore Prevent NPE exceptions by checking packetReader. ++ if (connection.packetReader != null) { ++ connection.packetReader.notifyConnectionError(ioe); ++ } ++ } ++ } ++ } ++ ++ /** ++ * Sends to the server a new stream element. This operation may be requested several times ++ * so we need to encapsulate the logic in one place. This message will be sent while doing ++ * TLS, SASL and resource binding. ++ * ++ * @throws IOException If an error occurs while sending the stanza to the server. ++ */ ++ void openStream() throws IOException { ++ StringBuilder stream = new StringBuilder(); ++ stream.append(""); ++ writer.write(stream.toString()); ++ writer.flush(); ++ } ++ ++ /** ++ * A TimerTask that keeps connections to the server alive by sending a space ++ * character on an interval. ++ */ ++ private class KeepAliveTask implements Runnable { ++ ++ private int delay; ++ private Thread thread; ++ ++ public KeepAliveTask(int delay) { ++ this.delay = delay; ++ } ++ ++ protected void setThread(Thread thread) { ++ this.thread = thread; ++ } ++ ++ public void run() { ++ try { ++ // Sleep a minimum of 15 seconds plus delay before sending first heartbeat. This will give time to ++ // properly finish TLS negotiation and then start sending heartbeats. ++ Thread.sleep(15000 + delay); ++ } ++ catch (InterruptedException ie) { ++ // Do nothing ++ } ++ while (!done && keepAliveThread == thread) { ++ synchronized (writer) { ++ // Send heartbeat if no packet has been sent to the server for a given time ++ if (System.currentTimeMillis() - lastActive >= delay) { ++ try { ++ writer.write(" "); ++ writer.flush(); ++ } ++ catch (Exception e) { ++ // Do nothing ++ } ++ } ++ } ++ try { ++ // Sleep until we should write the next keep-alive. ++ Thread.sleep(delay); ++ } ++ catch (InterruptedException ie) { ++ // Do nothing ++ } ++ } ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/PrivacyList.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/PrivacyList.java +new file mode 100644 +index 0000000..aec6636 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/PrivacyList.java +@@ -0,0 +1,57 @@ ++package org.jivesoftware.smack; ++ ++import org.jivesoftware.smack.packet.PrivacyItem; ++ ++import java.util.List; ++ ++/** ++ * A privacy list represents a list of contacts that is a read only class used to represent a set of allowed or blocked communications. ++ * Basically it can:

        ++ * ++ *
      • Handle many {@link org.jivesoftware.smack.packet.PrivacyItem}.
      • ++ *
      • Answer if it is the default list.
      • ++ *
      • Answer if it is the active list.
      • ++ *
      ++ * ++ * {@link PrivacyItem Privacy Items} can handle different kind of blocking communications based on JID, group, ++ * subscription type or globally. ++ * ++ * @author Francisco Vives ++ */ ++public class PrivacyList { ++ ++ /** Holds if it is an active list or not **/ ++ private boolean isActiveList; ++ /** Holds if it is an default list or not **/ ++ private boolean isDefaultList; ++ /** Holds the list name used to print **/ ++ private String listName; ++ /** Holds the list of {@see PrivacyItem} **/ ++ private List items; ++ ++ protected PrivacyList(boolean isActiveList, boolean isDefaultList, ++ String listName, List privacyItems) { ++ super(); ++ this.isActiveList = isActiveList; ++ this.isDefaultList = isDefaultList; ++ this.listName = listName; ++ this.items = privacyItems; ++ } ++ ++ public boolean isActiveList() { ++ return isActiveList; ++ } ++ ++ public boolean isDefaultList() { ++ return isDefaultList; ++ } ++ ++ public List getItems() { ++ return items; ++ } ++ ++ public String toString() { ++ return listName; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/PrivacyListListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/PrivacyListListener.java +new file mode 100644 +index 0000000..5644ed7 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/PrivacyListListener.java +@@ -0,0 +1,51 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2006-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.packet.PrivacyItem; ++ ++import java.util.List; ++ ++/** ++ * Interface to implement classes to listen for server events about privacy communication. ++ * Listeners are registered with the {@link PrivacyListManager}. ++ * ++ * @see PrivacyListManager#addListener ++ * ++ * @author Francisco Vives ++ */ ++public interface PrivacyListListener { ++ ++ /** ++ * Set or update a privacy list with PrivacyItem. ++ * ++ * @param listName the name of the new or updated privacy list. ++ * @param listItem the PrivacyItems that rules the list. ++ */ ++ public void setPrivacyList(String listName, List listItem); ++ ++ /** ++ * A privacy list has been modified by another. It gets notified. ++ * ++ * @param listName the name of the updated privacy list. ++ */ ++ public void updatedPrivacyList(String listName); ++ ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/PrivacyListManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/PrivacyListManager.java +new file mode 100644 +index 0000000..eb1d231 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/PrivacyListManager.java +@@ -0,0 +1,466 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2006-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.filter.*; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.Privacy; ++import org.jivesoftware.smack.packet.PrivacyItem; ++ ++import java.util.*; ++ ++/** ++ * A PrivacyListManager is used by XMPP clients to block or allow communications from other ++ * users. Use the manager to:
        ++ *
      • Retrieve privacy lists. ++ *
      • Add, remove, and edit privacy lists. ++ *
      • Set, change, or decline active lists. ++ *
      • Set, change, or decline the default list (i.e., the list that is active by default). ++ *
      ++ * Privacy Items can handle different kind of permission communications based on JID, group, ++ * subscription type or globally (@see PrivacyItem). ++ * ++ * @author Francisco Vives ++ */ ++public class PrivacyListManager { ++ ++ // Keep the list of instances of this class. ++ private static Map instances = new Hashtable(); ++ ++ private Connection connection; ++ private final List listeners = new ArrayList(); ++ PacketFilter packetFilter = new AndFilter(new IQTypeFilter(IQ.Type.SET), ++ new PacketExtensionFilter("query", "jabber:iq:privacy")); ++ ++ static { ++ // Create a new PrivacyListManager on every established connection. In the init() ++ // method of PrivacyListManager, we'll add a listener that will delete the ++ // instance when the connection is closed. ++ Connection.addConnectionCreationListener(new ConnectionCreationListener() { ++ public void connectionCreated(Connection connection) { ++ new PrivacyListManager(connection); ++ } ++ }); ++ } ++ /** ++ * Creates a new privacy manager to maintain the communication privacy. Note: no ++ * information is sent to or received from the server until you attempt to ++ * get or set the privacy communication.

      ++ * ++ * @param connection the XMPP connection. ++ */ ++ private PrivacyListManager(Connection connection) { ++ this.connection = connection; ++ this.init(); ++ } ++ ++ /** Answer the connection userJID that owns the privacy. ++ * @return the userJID that owns the privacy ++ */ ++ private String getUser() { ++ return connection.getUser(); ++ } ++ ++ /** ++ * Initializes the packet listeners of the connection that will notify for any set privacy ++ * package. ++ */ ++ private void init() { ++ // Register the new instance and associate it with the connection ++ instances.put(connection, this); ++ // Add a listener to the connection that removes the registered instance when ++ // the connection is closed ++ connection.addConnectionListener(new ConnectionListener() { ++ public void connectionClosed() { ++ // Unregister this instance since the connection has been closed ++ instances.remove(connection); ++ } ++ ++ public void connectionClosedOnError(Exception e) { ++ // ignore ++ } ++ ++ public void reconnectionFailed(Exception e) { ++ // ignore ++ } ++ ++ public void reconnectingIn(int seconds) { ++ // ignore ++ } ++ ++ public void reconnectionSuccessful() { ++ // ignore ++ } ++ }); ++ ++ connection.addPacketListener(new PacketListener() { ++ public void processPacket(Packet packet) { ++ ++ if (packet == null || packet.getError() != null) { ++ return; ++ } ++ // The packet is correct. ++ Privacy privacy = (Privacy) packet; ++ ++ // Notifies the event to the listeners. ++ synchronized (listeners) { ++ for (PrivacyListListener listener : listeners) { ++ // Notifies the created or updated privacy lists ++ for (Map.Entry> entry : privacy.getItemLists().entrySet()) { ++ String listName = entry.getKey(); ++ List items = entry.getValue(); ++ if (items.isEmpty()) { ++ listener.updatedPrivacyList(listName); ++ } else { ++ listener.setPrivacyList(listName, items); ++ } ++ } ++ } ++ } ++ ++ // Send a result package acknowledging the reception of a privacy package. ++ ++ // Prepare the IQ packet to send ++ IQ iq = new IQ() { ++ public String getChildElementXML() { ++ return ""; ++ } ++ }; ++ iq.setType(IQ.Type.RESULT); ++ iq.setFrom(packet.getFrom()); ++ iq.setPacketID(packet.getPacketID()); ++ ++ // Send create & join packet. ++ connection.sendPacket(iq); ++ } ++ }, packetFilter); ++ } ++ ++ /** ++ * Returns the PrivacyListManager instance associated with a given Connection. ++ * ++ * @param connection the connection used to look for the proper PrivacyListManager. ++ * @return the PrivacyListManager associated with a given Connection. ++ */ ++ public static PrivacyListManager getInstanceFor(Connection connection) { ++ return instances.get(connection); ++ } ++ ++ /** ++ * Send the {@link Privacy} packet to the server in order to know some privacy content and then ++ * waits for the answer. ++ * ++ * @param requestPrivacy is the {@link Privacy} packet configured properly whose XML ++ * will be sent to the server. ++ * @return a new {@link Privacy} with the data received from the server. ++ * @exception XMPPException if the request or the answer failed, it raises an exception. ++ */ ++ private Privacy getRequest(Privacy requestPrivacy) throws XMPPException { ++ // The request is a get iq type ++ requestPrivacy.setType(Privacy.Type.GET); ++ requestPrivacy.setFrom(this.getUser()); ++ ++ // Filter packets looking for an answer from the server. ++ PacketFilter responseFilter = new PacketIDFilter(requestPrivacy.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ ++ // Send create & join packet. ++ connection.sendPacket(requestPrivacy); ++ ++ // Wait up to a certain number of seconds for a reply. ++ Privacy privacyAnswer = ++ (Privacy) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Stop queuing results ++ response.cancel(); ++ ++ // Interprete the result and answer the privacy only if it is valid ++ if (privacyAnswer == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (privacyAnswer.getError() != null) { ++ throw new XMPPException(privacyAnswer.getError()); ++ } ++ return privacyAnswer; ++ } ++ ++ /** ++ * Send the {@link Privacy} packet to the server in order to modify the server privacy and ++ * waits for the answer. ++ * ++ * @param requestPrivacy is the {@link Privacy} packet configured properly whose xml will be sent ++ * to the server. ++ * @return a new {@link Privacy} with the data received from the server. ++ * @exception XMPPException if the request or the answer failed, it raises an exception. ++ */ ++ private Packet setRequest(Privacy requestPrivacy) throws XMPPException { ++ ++ // The request is a get iq type ++ requestPrivacy.setType(Privacy.Type.SET); ++ requestPrivacy.setFrom(this.getUser()); ++ ++ // Filter packets looking for an answer from the server. ++ PacketFilter responseFilter = new PacketIDFilter(requestPrivacy.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ ++ // Send create & join packet. ++ connection.sendPacket(requestPrivacy); ++ ++ // Wait up to a certain number of seconds for a reply. ++ Packet privacyAnswer = response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Stop queuing results ++ response.cancel(); ++ ++ // Interprete the result and answer the privacy only if it is valid ++ if (privacyAnswer == null) { ++ throw new XMPPException("No response from server."); ++ } else if (privacyAnswer.getError() != null) { ++ throw new XMPPException(privacyAnswer.getError()); ++ } ++ return privacyAnswer; ++ } ++ ++ /** ++ * Answer a privacy containing the list structre without {@link PrivacyItem}. ++ * ++ * @return a Privacy with the list names. ++ * @throws XMPPException if an error occurs. ++ */ ++ private Privacy getPrivacyWithListNames() throws XMPPException { ++ ++ // The request of the list is an empty privacy message ++ Privacy request = new Privacy(); ++ ++ // Send the package to the server and get the answer ++ return getRequest(request); ++ } ++ ++ /** ++ * Answer the active privacy list. ++ * ++ * @return the privacy list of the active list. ++ * @throws XMPPException if an error occurs. ++ */ ++ public PrivacyList getActiveList() throws XMPPException { ++ Privacy privacyAnswer = this.getPrivacyWithListNames(); ++ String listName = privacyAnswer.getActiveName(); ++ boolean isDefaultAndActive = privacyAnswer.getActiveName() != null ++ && privacyAnswer.getDefaultName() != null ++ && privacyAnswer.getActiveName().equals( ++ privacyAnswer.getDefaultName()); ++ return new PrivacyList(true, isDefaultAndActive, listName, getPrivacyListItems(listName)); ++ } ++ ++ /** ++ * Answer the default privacy list. ++ * ++ * @return the privacy list of the default list. ++ * @throws XMPPException if an error occurs. ++ */ ++ public PrivacyList getDefaultList() throws XMPPException { ++ Privacy privacyAnswer = this.getPrivacyWithListNames(); ++ String listName = privacyAnswer.getDefaultName(); ++ boolean isDefaultAndActive = privacyAnswer.getActiveName() != null ++ && privacyAnswer.getDefaultName() != null ++ && privacyAnswer.getActiveName().equals( ++ privacyAnswer.getDefaultName()); ++ return new PrivacyList(isDefaultAndActive, true, listName, getPrivacyListItems(listName)); ++ } ++ ++ /** ++ * Answer the privacy list items under listName with the allowed and blocked permissions. ++ * ++ * @param listName the name of the list to get the allowed and blocked permissions. ++ * @return a list of privacy items under the list listName. ++ * @throws XMPPException if an error occurs. ++ */ ++ private List getPrivacyListItems(String listName) throws XMPPException { ++ ++ // The request of the list is an privacy message with an empty list ++ Privacy request = new Privacy(); ++ request.setPrivacyList(listName, new ArrayList()); ++ ++ // Send the package to the server and get the answer ++ Privacy privacyAnswer = getRequest(request); ++ ++ return privacyAnswer.getPrivacyList(listName); ++ } ++ ++ /** ++ * Answer the privacy list items under listName with the allowed and blocked permissions. ++ * ++ * @param listName the name of the list to get the allowed and blocked permissions. ++ * @return a privacy list under the list listName. ++ * @throws XMPPException if an error occurs. ++ */ ++ public PrivacyList getPrivacyList(String listName) throws XMPPException { ++ ++ return new PrivacyList(false, false, listName, getPrivacyListItems(listName)); ++ } ++ ++ /** ++ * Answer every privacy list with the allowed and blocked permissions. ++ * ++ * @return an array of privacy lists. ++ * @throws XMPPException if an error occurs. ++ */ ++ public PrivacyList[] getPrivacyLists() throws XMPPException { ++ Privacy privacyAnswer = this.getPrivacyWithListNames(); ++ Set names = privacyAnswer.getPrivacyListNames(); ++ PrivacyList[] lists = new PrivacyList[names.size()]; ++ boolean isActiveList; ++ boolean isDefaultList; ++ int index=0; ++ for (String listName : names) { ++ isActiveList = listName.equals(privacyAnswer.getActiveName()); ++ isDefaultList = listName.equals(privacyAnswer.getDefaultName()); ++ lists[index] = new PrivacyList(isActiveList, isDefaultList, ++ listName, getPrivacyListItems(listName)); ++ index = index + 1; ++ } ++ return lists; ++ } ++ ++ ++ /** ++ * Set or change the active list to listName. ++ * ++ * @param listName the list name to set as the active one. ++ * @exception XMPPException if the request or the answer failed, it raises an exception. ++ */ ++ public void setActiveListName(String listName) throws XMPPException { ++ ++ // The request of the list is an privacy message with an empty list ++ Privacy request = new Privacy(); ++ request.setActiveName(listName); ++ ++ // Send the package to the server ++ setRequest(request); ++ } ++ ++ /** ++ * Client declines the use of active lists. ++ * ++ * @throws XMPPException if an error occurs. ++ */ ++ public void declineActiveList() throws XMPPException { ++ ++ // The request of the list is an privacy message with an empty list ++ Privacy request = new Privacy(); ++ request.setDeclineActiveList(true); ++ ++ // Send the package to the server ++ setRequest(request); ++ } ++ ++ /** ++ * Set or change the default list to listName. ++ * ++ * @param listName the list name to set as the default one. ++ * @exception XMPPException if the request or the answer failed, it raises an exception. ++ */ ++ public void setDefaultListName(String listName) throws XMPPException { ++ ++ // The request of the list is an privacy message with an empty list ++ Privacy request = new Privacy(); ++ request.setDefaultName(listName); ++ ++ // Send the package to the server ++ setRequest(request); ++ } ++ ++ /** ++ * Client declines the use of default lists. ++ * ++ * @throws XMPPException if an error occurs. ++ */ ++ public void declineDefaultList() throws XMPPException { ++ ++ // The request of the list is an privacy message with an empty list ++ Privacy request = new Privacy(); ++ request.setDeclineDefaultList(true); ++ ++ // Send the package to the server ++ setRequest(request); ++ } ++ ++ /** ++ * The client has created a new list. It send the new one to the server. ++ * ++ * @param listName the list that has changed its content. ++ * @param privacyItems a List with every privacy item in the list. ++ * @throws XMPPException if an error occurs. ++ */ ++ public void createPrivacyList(String listName, List privacyItems) throws XMPPException { ++ ++ this.updatePrivacyList(listName, privacyItems); ++ } ++ ++ /** ++ * The client has edited an existing list. It updates the server content with the resulting ++ * list of privacy items. The {@link PrivacyItem} list MUST contain all elements in the ++ * list (not the "delta"). ++ * ++ * @param listName the list that has changed its content. ++ * @param privacyItems a List with every privacy item in the list. ++ * @throws XMPPException if an error occurs. ++ */ ++ public void updatePrivacyList(String listName, List privacyItems) throws XMPPException { ++ ++ // Build the privacy package to add or update the new list ++ Privacy request = new Privacy(); ++ request.setPrivacyList(listName, privacyItems); ++ ++ // Send the package to the server ++ setRequest(request); ++ } ++ ++ /** ++ * Remove a privacy list. ++ * ++ * @param listName the list that has changed its content. ++ * @throws XMPPException if an error occurs. ++ */ ++ public void deletePrivacyList(String listName) throws XMPPException { ++ ++ // The request of the list is an privacy message with an empty list ++ Privacy request = new Privacy(); ++ request.setPrivacyList(listName, new ArrayList()); ++ ++ // Send the package to the server ++ setRequest(request); ++ } ++ ++ /** ++ * Adds a packet listener that will be notified of any new update in the user ++ * privacy communication. ++ * ++ * @param listener a packet listener. ++ */ ++ public void addListener(PrivacyListListener listener) { ++ // Keep track of the listener so that we can manually deliver extra ++ // messages to it later if needed. ++ synchronized (listeners) { ++ listeners.add(listener); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/ReconnectionManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/ReconnectionManager.java +new file mode 100644 +index 0000000..514c823 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/ReconnectionManager.java +@@ -0,0 +1,210 @@ ++package org.jivesoftware.smack; ++ ++import org.jivesoftware.smack.packet.StreamError; ++import java.util.Random; ++/** ++ * Handles the automatic reconnection process. Every time a connection is dropped without ++ * the application explictly closing it, the manager automatically tries to reconnect to ++ * the server.

      ++ * ++ * The reconnection mechanism will try to reconnect periodically: ++ *

        ++ *
      1. For the first minute it will attempt to connect once every ten seconds. ++ *
      2. For the next five minutes it will attempt to connect once a minute. ++ *
      3. If that fails it will indefinitely try to connect once every five minutes. ++ *
      ++ * ++ * @author Francisco Vives ++ */ ++public class ReconnectionManager implements ConnectionListener { ++ ++ // Holds the connection to the server ++ private Connection connection; ++ private Thread reconnectionThread; ++ private int randomBase = new Random().nextInt(11) + 5; // between 5 and 15 seconds ++ ++ // Holds the state of the reconnection ++ boolean done = false; ++ ++ static { ++ // Create a new PrivacyListManager on every established connection. In the init() ++ // method of PrivacyListManager, we'll add a listener that will delete the ++ // instance when the connection is closed. ++ Connection.addConnectionCreationListener(new ConnectionCreationListener() { ++ public void connectionCreated(Connection connection) { ++ connection.addConnectionListener(new ReconnectionManager(connection)); ++ } ++ }); ++ } ++ ++ private ReconnectionManager(Connection connection) { ++ this.connection = connection; ++ } ++ ++ ++ /** ++ * Returns true if the reconnection mechanism is enabled. ++ * ++ * @return true if automatic reconnections are allowed. ++ */ ++ private boolean isReconnectionAllowed() { ++ return !done && !connection.isConnected() ++ && connection.isReconnectionAllowed(); ++ } ++ ++ /** ++ * Starts a reconnection mechanism if it was configured to do that. ++ * The algorithm is been executed when the first connection error is detected. ++ *

      ++ * The reconnection mechanism will try to reconnect periodically in this way: ++ *

        ++ *
      1. First it will try 6 times every 10 seconds. ++ *
      2. Then it will try 10 times every 1 minute. ++ *
      3. Finally it will try indefinitely every 5 minutes. ++ *
      ++ */ ++ synchronized protected void reconnect() { ++ if (this.isReconnectionAllowed()) { ++ // Since there is no thread running, creates a new one to attempt ++ // the reconnection. ++ // avoid to run duplicated reconnectionThread -- fd: 16/09/2010 ++ if (reconnectionThread!=null && reconnectionThread.isAlive()) return; ++ ++ reconnectionThread = new Thread() { ++ ++ /** ++ * Holds the current number of reconnection attempts ++ */ ++ private int attempts = 0; ++ ++ /** ++ * Returns the number of seconds until the next reconnection attempt. ++ * ++ * @return the number of seconds until the next reconnection attempt. ++ */ ++ private int timeDelay() { ++ attempts++; ++ if (attempts > 13) { ++ return randomBase*6*5; // between 2.5 and 7.5 minutes (~5 minutes) ++ } ++ if (attempts > 7) { ++ return randomBase*6; // between 30 and 90 seconds (~1 minutes) ++ } ++ return randomBase; // 10 seconds ++ } ++ ++ /** ++ * The process will try the reconnection until the connection succeed or the user ++ * cancell it ++ */ ++ public void run() { ++ // The process will try to reconnect until the connection is established or ++ // the user cancel the reconnection process {@link Connection#disconnect()} ++ while (ReconnectionManager.this.isReconnectionAllowed()) { ++ // Find how much time we should wait until the next reconnection ++ int remainingSeconds = timeDelay(); ++ // Sleep until we're ready for the next reconnection attempt. Notify ++ // listeners once per second about how much time remains before the next ++ // reconnection attempt. ++ while (ReconnectionManager.this.isReconnectionAllowed() && ++ remainingSeconds > 0) ++ { ++ try { ++ Thread.sleep(1000); ++ remainingSeconds--; ++ ReconnectionManager.this ++ .notifyAttemptToReconnectIn(remainingSeconds); ++ } ++ catch (InterruptedException e1) { ++ e1.printStackTrace(); ++ // Notify the reconnection has failed ++ ReconnectionManager.this.notifyReconnectionFailed(e1); ++ } ++ } ++ ++ // Makes a reconnection attempt ++ try { ++ if (ReconnectionManager.this.isReconnectionAllowed()) { ++ connection.connect(); ++ } ++ } ++ catch (XMPPException e) { ++ // Fires the failed reconnection notification ++ ReconnectionManager.this.notifyReconnectionFailed(e); ++ } ++ } ++ } ++ }; ++ reconnectionThread.setName("Smack Reconnection Manager"); ++ reconnectionThread.setDaemon(true); ++ reconnectionThread.start(); ++ } ++ } ++ ++ /** ++ * Fires listeners when a reconnection attempt has failed. ++ * ++ * @param exception the exception that occured. ++ */ ++ protected void notifyReconnectionFailed(Exception exception) { ++ if (isReconnectionAllowed()) { ++ for (ConnectionListener listener : connection.connectionListeners) { ++ listener.reconnectionFailed(exception); ++ } ++ } ++ } ++ ++ /** ++ * Fires listeners when The Connection will retry a reconnection. Expressed in seconds. ++ * ++ * @param seconds the number of seconds that a reconnection will be attempted in. ++ */ ++ protected void notifyAttemptToReconnectIn(int seconds) { ++ if (isReconnectionAllowed()) { ++ for (ConnectionListener listener : connection.connectionListeners) { ++ listener.reconnectingIn(seconds); ++ } ++ } ++ } ++ ++ public void connectionClosed() { ++ done = true; ++ } ++ ++ public void connectionClosedOnError(Exception e) { ++ done = false; ++ if (e instanceof XMPPException) { ++ XMPPException xmppEx = (XMPPException) e; ++ StreamError error = xmppEx.getStreamError(); ++ ++ // Make sure the error is not null ++ if (error != null) { ++ String reason = error.getCode(); ++ ++ if ("conflict".equals(reason)) { ++ return; ++ } ++ } ++ } ++ ++ if (this.isReconnectionAllowed()) { ++ this.reconnect(); ++ } ++ } ++ ++ public void reconnectingIn(int seconds) { ++ // ignore ++ } ++ ++ public void reconnectionFailed(Exception e) { ++ // ignore ++ } ++ ++ /** ++ * The connection has successfull gotten connected. ++ */ ++ public void reconnectionSuccessful() { ++ // ignore ++ } ++ ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/Roster.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/Roster.java +new file mode 100644 +index 0000000..8f651cc +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/Roster.java +@@ -0,0 +1,1045 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.Presence; ++import org.jivesoftware.smack.packet.RosterPacket; ++import org.jivesoftware.smack.util.StringUtils; ++ ++import java.util.*; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.CopyOnWriteArrayList; ++ ++/** ++ * Represents a user's roster, which is the collection of users a person receives ++ * presence updates for. Roster items are categorized into groups for easier management.

      ++ *

      ++ * Others users may attempt to subscribe to this user using a subscription request. Three ++ * modes are supported for handling these requests:

        ++ *
      • {@link SubscriptionMode#accept_all accept_all} -- accept all subscription requests.
      • ++ *
      • {@link SubscriptionMode#reject_all reject_all} -- reject all subscription requests.
      • ++ *
      • {@link SubscriptionMode#manual manual} -- manually process all subscription requests.
      • ++ *
      ++ * ++ * @author Matt Tucker ++ * @see Connection#getRoster() ++ */ ++public class Roster { ++ ++ /** ++ * The default subscription processing mode to use when a Roster is created. By default ++ * all subscription requests are automatically accepted. ++ */ ++ private static SubscriptionMode defaultSubscriptionMode = SubscriptionMode.accept_all; ++ private RosterStorage persistentStorage; ++ ++ private Connection connection; ++ private final Map groups; ++ private final Map entries; ++ private final List unfiledEntries; ++ private final List rosterListeners; ++ private Map> presenceMap; ++ // The roster is marked as initialized when at least a single roster packet ++ // has been received and processed. ++ boolean rosterInitialized = false; ++ private PresencePacketListener presencePacketListener; ++ ++ private SubscriptionMode subscriptionMode = getDefaultSubscriptionMode(); ++ ++ private String requestPacketId; ++ ++ private boolean mOfflineOnError = true; ++ ++ /** ++ * Returns the default subscription processing mode to use when a new Roster is created. The ++ * subscription processing mode dictates what action Smack will take when subscription ++ * requests from other users are made. The default subscription mode ++ * is {@link SubscriptionMode#accept_all}. ++ * ++ * @return the default subscription mode to use for new Rosters ++ */ ++ public static SubscriptionMode getDefaultSubscriptionMode() { ++ return defaultSubscriptionMode; ++ } ++ ++ /** ++ * Sets the default subscription processing mode to use when a new Roster is created. The ++ * subscription processing mode dictates what action Smack will take when subscription ++ * requests from other users are made. The default subscription mode ++ * is {@link SubscriptionMode#accept_all}. ++ * ++ * @param subscriptionMode the default subscription mode to use for new Rosters. ++ */ ++ public static void setDefaultSubscriptionMode(SubscriptionMode subscriptionMode) { ++ defaultSubscriptionMode = subscriptionMode; ++ } ++ ++ Roster(final Connection connection, RosterStorage persistentStorage){ ++ this(connection); ++ this.persistentStorage = persistentStorage; ++ } ++ ++ /** ++ * Creates a new roster. ++ * ++ * @param connection an XMPP connection. ++ */ ++ Roster(final Connection connection) { ++ this.connection = connection; ++ //Disable roster versioning if server doesn't offer support for it ++ if(!connection.getConfiguration().isRosterVersioningAvailable()){ ++ persistentStorage=null; ++ } ++ groups = new ConcurrentHashMap(); ++ unfiledEntries = new CopyOnWriteArrayList(); ++ entries = new ConcurrentHashMap(); ++ rosterListeners = new CopyOnWriteArrayList(); ++ presenceMap = new ConcurrentHashMap>(); ++ // Listen for any roster packets. ++ PacketFilter rosterFilter = new PacketTypeFilter(RosterPacket.class); ++ connection.addPacketListener(new RosterPacketListener(), rosterFilter); ++ // Listen for any presence packets. ++ PacketFilter presenceFilter = new PacketTypeFilter(Presence.class); ++ presencePacketListener = new PresencePacketListener(); ++ connection.addPacketListener(presencePacketListener, presenceFilter); ++ ++ // Listen for connection events ++ final ConnectionListener connectionListener = new AbstractConnectionListener() { ++ ++ public void connectionClosed() { ++ // Changes the presence available contacts to unavailable ++ setOfflinePresences(); ++ } ++ ++ public void connectionClosedOnError(Exception e) { ++ // Changes the presence available contacts to unavailable ++ if (mOfflineOnError) ++ setOfflinePresences(); ++ } ++ ++ }; ++ ++ // if not connected add listener after successful login ++ if(!this.connection.isConnected()) { ++ Connection.addConnectionCreationListener(new ConnectionCreationListener() { ++ ++ public void connectionCreated(Connection connection) { ++ if(connection.equals(Roster.this.connection)) { ++ Roster.this.connection.addConnectionListener(connectionListener); ++ } ++ ++ } ++ }); ++ } else { ++ connection.addConnectionListener(connectionListener); ++ } ++ } ++ ++ /** ++ * Returns the subscription processing mode, which dictates what action ++ * Smack will take when subscription requests from other users are made. ++ * The default subscription mode is {@link SubscriptionMode#accept_all}.

      ++ *

      ++ * If using the manual mode, a PacketListener should be registered that ++ * listens for Presence packets that have a type of ++ * {@link org.jivesoftware.smack.packet.Presence.Type#subscribe}. ++ * ++ * @return the subscription mode. ++ */ ++ public SubscriptionMode getSubscriptionMode() { ++ return subscriptionMode; ++ } ++ ++ /** ++ * Sets the subscription processing mode, which dictates what action ++ * Smack will take when subscription requests from other users are made. ++ * The default subscription mode is {@link SubscriptionMode#accept_all}.

      ++ *

      ++ * If using the manual mode, a PacketListener should be registered that ++ * listens for Presence packets that have a type of ++ * {@link org.jivesoftware.smack.packet.Presence.Type#subscribe}. ++ * ++ * @param subscriptionMode the subscription mode. ++ */ ++ public void setSubscriptionMode(SubscriptionMode subscriptionMode) { ++ this.subscriptionMode = subscriptionMode; ++ } ++ ++ public void setOfflineOnError(boolean offlineOnError) { ++ this.mOfflineOnError = offlineOnError; ++ } ++ ++ /** ++ * Reloads the entire roster from the server. This is an asynchronous operation, ++ * which means the method will return immediately, and the roster will be ++ * reloaded at a later point when the server responds to the reload request. ++ * ++ * @throws IllegalStateException if connection is not logged in or logged in anonymously ++ */ ++ public void reload() { ++ if (!connection.isAuthenticated()) { ++ throw new IllegalStateException("Not logged in to server."); ++ } ++ if (connection.isAnonymous()) { ++ throw new IllegalStateException("Anonymous users can't have a roster."); ++ } ++ ++ RosterPacket packet = new RosterPacket(); ++ if(persistentStorage!=null){ ++ packet.setVersion(persistentStorage.getRosterVersion()); ++ } ++ requestPacketId = packet.getPacketID(); ++ PacketFilter idFilter = new PacketIDFilter(requestPacketId); ++ connection.addPacketListener(new RosterResultListener(), idFilter); ++ connection.sendPacket(packet); ++ } ++ ++ /** ++ * Adds a listener to this roster. The listener will be fired anytime one or more ++ * changes to the roster are pushed from the server. ++ * ++ * @param rosterListener a roster listener. ++ */ ++ public void addRosterListener(RosterListener rosterListener) { ++ if (!rosterListeners.contains(rosterListener)) { ++ rosterListeners.add(rosterListener); ++ } ++ } ++ ++ /** ++ * Removes a listener from this roster. The listener will be fired anytime one or more ++ * changes to the roster are pushed from the server. ++ * ++ * @param rosterListener a roster listener. ++ */ ++ public void removeRosterListener(RosterListener rosterListener) { ++ rosterListeners.remove(rosterListener); ++ } ++ ++ /** ++ * Creates a new group.

      ++ *

      ++ * Note: you must add at least one entry to the group for the group to be kept ++ * after a logout/login. This is due to the way that XMPP stores group information. ++ * ++ * @param name the name of the group. ++ * @return a new group. ++ * @throws IllegalStateException if connection is not logged in or logged in anonymously ++ */ ++ public RosterGroup createGroup(String name) { ++ if (!connection.isAuthenticated()) { ++ throw new IllegalStateException("Not logged in to server."); ++ } ++ if (connection.isAnonymous()) { ++ throw new IllegalStateException("Anonymous users can't have a roster."); ++ } ++ if (groups.containsKey(name)) { ++ throw new IllegalArgumentException("Group with name " + name + " alread exists."); ++ } ++ ++ RosterGroup group = new RosterGroup(name, connection); ++ groups.put(name, group); ++ return group; ++ } ++ ++ /** ++ * Creates a new roster entry and presence subscription. The server will asynchronously ++ * update the roster with the subscription status. ++ * ++ * @param user the user. (e.g. johndoe@jabber.org) ++ * @param name the nickname of the user. ++ * @param groups the list of group names the entry will belong to, or null if the ++ * the roster entry won't belong to a group. ++ * @throws XMPPException if an XMPP exception occurs. ++ * @throws IllegalStateException if connection is not logged in or logged in anonymously ++ */ ++ public void createEntry(String user, String name, String[] groups) throws XMPPException { ++ if (!connection.isAuthenticated()) { ++ throw new IllegalStateException("Not logged in to server."); ++ } ++ if (connection.isAnonymous()) { ++ throw new IllegalStateException("Anonymous users can't have a roster."); ++ } ++ ++ // Create and send roster entry creation packet. ++ RosterPacket rosterPacket = new RosterPacket(); ++ rosterPacket.setType(IQ.Type.SET); ++ RosterPacket.Item item = new RosterPacket.Item(user, name); ++ if (groups != null) { ++ for (String group : groups) { ++ if (group != null && group.trim().length() > 0) { ++ item.addGroupName(group); ++ } ++ } ++ } ++ rosterPacket.addRosterItem(item); ++ // Wait up to a certain number of seconds for a reply from the server. ++ PacketCollector collector = connection.createPacketCollector( ++ new PacketIDFilter(rosterPacket.getPacketID())); ++ connection.sendPacket(rosterPacket); ++ IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ // If the server replied with an error, throw an exception. ++ else if (response.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(response.getError()); ++ } ++ ++ // Create a presence subscription packet and send. ++ Presence presencePacket = new Presence(Presence.Type.subscribe); ++ presencePacket.setTo(user); ++ connection.sendPacket(presencePacket); ++ } ++ ++ private void insertRosterItems(List items){ ++ Collection addedEntries = new ArrayList(); ++ Collection updatedEntries = new ArrayList(); ++ Collection deletedEntries = new ArrayList(); ++ Iterator iter = items.iterator(); ++ while(iter.hasNext()){ ++ insertRosterItem(iter.next(), addedEntries,updatedEntries,deletedEntries); ++ } ++ fireRosterChangedEvent(addedEntries, updatedEntries, deletedEntries); ++ } ++ ++ private void insertRosterItem(RosterPacket.Item item, Collection addedEntries, ++ Collection updatedEntries, Collection deletedEntries){ ++ RosterEntry entry = new RosterEntry(item.getUser(), item.getName(), ++ item.getItemType(), item.getItemStatus(), this, connection); ++ ++ // If the packet is of the type REMOVE then remove the entry ++ if (RosterPacket.ItemType.remove.equals(item.getItemType())) { ++ // Remove the entry from the entry list. ++ if (entries.containsKey(item.getUser())) { ++ entries.remove(item.getUser()); ++ } ++ // Remove the entry from the unfiled entry list. ++ if (unfiledEntries.contains(entry)) { ++ unfiledEntries.remove(entry); ++ } ++ // Removing the user from the roster, so remove any presence information ++ // about them. ++ String key = StringUtils.parseName(item.getUser()) + "@" + ++ StringUtils.parseServer(item.getUser()); ++ presenceMap.remove(key); ++ // Keep note that an entry has been removed ++ if(deletedEntries!=null){ ++ deletedEntries.add(item.getUser()); ++ } ++ } ++ else { ++ // Make sure the entry is in the entry list. ++ if (!entries.containsKey(item.getUser())) { ++ entries.put(item.getUser(), entry); ++ // Keep note that an entry has been added ++ if(addedEntries!=null){ ++ addedEntries.add(item.getUser()); ++ } ++ } ++ else { ++ // If the entry was in then list then update its state with the new values ++ entries.put(item.getUser(), entry); ++ ++ // Keep note that an entry has been updated ++ if(updatedEntries!=null){ ++ updatedEntries.add(item.getUser()); ++ } ++ } ++ // If the roster entry belongs to any groups, remove it from the ++ // list of unfiled entries. ++ if (!item.getGroupNames().isEmpty()) { ++ unfiledEntries.remove(entry); ++ } ++ // Otherwise add it to the list of unfiled entries. ++ else { ++ if (!unfiledEntries.contains(entry)) { ++ unfiledEntries.add(entry); ++ } ++ } ++ } ++ ++ // Find the list of groups that the user currently belongs to. ++ List currentGroupNames = new ArrayList(); ++ for (RosterGroup group: getGroups()) { ++ if (group.contains(entry)) { ++ currentGroupNames.add(group.getName()); ++ } ++ } ++ ++ // If the packet is not of the type REMOVE then add the entry to the groups ++ if (!RosterPacket.ItemType.remove.equals(item.getItemType())) { ++ // Create the new list of groups the user belongs to. ++ List newGroupNames = new ArrayList(); ++ for (String groupName : item.getGroupNames()) { ++ // Add the group name to the list. ++ newGroupNames.add(groupName); ++ ++ // Add the entry to the group. ++ RosterGroup group = getGroup(groupName); ++ if (group == null) { ++ group = createGroup(groupName); ++ groups.put(groupName, group); ++ } ++ // Add the entry. ++ group.addEntryLocal(entry); ++ } ++ ++ // We have the list of old and new group names. We now need to ++ // remove the entry from the all the groups it may no longer belong ++ // to. We do this by subracting the new group set from the old. ++ for (String newGroupName : newGroupNames) { ++ currentGroupNames.remove(newGroupName); ++ } ++ } ++ ++ // Loop through any groups that remain and remove the entries. ++ // This is neccessary for the case of remote entry removals. ++ for (String groupName : currentGroupNames) { ++ RosterGroup group = getGroup(groupName); ++ group.removeEntryLocal(entry); ++ if (group.getEntryCount() == 0) { ++ groups.remove(groupName); ++ } ++ } ++ // Remove all the groups with no entries. We have to do this because ++ // RosterGroup.removeEntry removes the entry immediately (locally) and the ++ // group could remain empty. ++ // TODO Check the performance/logic for rosters with large number of groups ++ for (RosterGroup group : getGroups()) { ++ if (group.getEntryCount() == 0) { ++ groups.remove(group.getName()); ++ } ++ } ++ } ++ ++ /** ++ * Removes a roster entry from the roster. The roster entry will also be removed from the ++ * unfiled entries or from any roster group where it could belong and will no longer be part ++ * of the roster. Note that this is an asynchronous call -- Smack must wait for the server ++ * to send an updated subscription status. ++ * ++ * @param entry a roster entry. ++ * @throws XMPPException if an XMPP error occurs. ++ * @throws IllegalStateException if connection is not logged in or logged in anonymously ++ */ ++ public void removeEntry(RosterEntry entry) throws XMPPException { ++ if (!connection.isAuthenticated()) { ++ throw new IllegalStateException("Not logged in to server."); ++ } ++ if (connection.isAnonymous()) { ++ throw new IllegalStateException("Anonymous users can't have a roster."); ++ } ++ ++ // Only remove the entry if it's in the entry list. ++ // The actual removal logic takes place in RosterPacketListenerprocess>>Packet(Packet) ++ if (!entries.containsKey(entry.getUser())) { ++ return; ++ } ++ RosterPacket packet = new RosterPacket(); ++ packet.setType(IQ.Type.SET); ++ RosterPacket.Item item = RosterEntry.toRosterItem(entry); ++ // Set the item type as REMOVE so that the server will delete the entry ++ item.setItemType(RosterPacket.ItemType.remove); ++ packet.addRosterItem(item); ++ PacketCollector collector = connection.createPacketCollector( ++ new PacketIDFilter(packet.getPacketID())); ++ connection.sendPacket(packet); ++ IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ // If the server replied with an error, throw an exception. ++ else if (response.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(response.getError()); ++ } ++ } ++ ++ /** ++ * Returns a count of the entries in the roster. ++ * ++ * @return the number of entries in the roster. ++ */ ++ public int getEntryCount() { ++ return getEntries().size(); ++ } ++ ++ /** ++ * Returns an unmodifiable collection of all entries in the roster, including entries ++ * that don't belong to any groups. ++ * ++ * @return all entries in the roster. ++ */ ++ public Collection getEntries() { ++ Set allEntries = new HashSet(); ++ // Loop through all roster groups and add their entries to the answer ++ for (RosterGroup rosterGroup : getGroups()) { ++ allEntries.addAll(rosterGroup.getEntries()); ++ } ++ // Add the roster unfiled entries to the answer ++ allEntries.addAll(unfiledEntries); ++ ++ return Collections.unmodifiableCollection(allEntries); ++ } ++ ++ /** ++ * Returns a count of the unfiled entries in the roster. An unfiled entry is ++ * an entry that doesn't belong to any groups. ++ * ++ * @return the number of unfiled entries in the roster. ++ */ ++ public int getUnfiledEntryCount() { ++ return unfiledEntries.size(); ++ } ++ ++ /** ++ * Returns an unmodifiable collection for the unfiled roster entries. An unfiled entry is ++ * an entry that doesn't belong to any groups. ++ * ++ * @return the unfiled roster entries. ++ */ ++ public Collection getUnfiledEntries() { ++ return Collections.unmodifiableList(unfiledEntries); ++ } ++ ++ /** ++ * Returns the roster entry associated with the given XMPP address or ++ * null if the user is not an entry in the roster. ++ * ++ * @param user the XMPP address of the user (eg "jsmith@example.com"). The address could be ++ * in any valid format (e.g. "domain/resource", "user@domain" or "user@domain/resource"). ++ * @return the roster entry or null if it does not exist. ++ */ ++ public RosterEntry getEntry(String user) { ++ if (user == null) { ++ return null; ++ } ++ return entries.get(user.toLowerCase()); ++ } ++ ++ /** ++ * Returns true if the specified XMPP address is an entry in the roster. ++ * ++ * @param user the XMPP address of the user (eg "jsmith@example.com"). The ++ * address could be in any valid format (e.g. "domain/resource", ++ * "user@domain" or "user@domain/resource"). ++ * @return true if the XMPP address is an entry in the roster. ++ */ ++ public boolean contains(String user) { ++ return getEntry(user) != null; ++ } ++ ++ /** ++ * Returns the roster group with the specified name, or null if the ++ * group doesn't exist. ++ * ++ * @param name the name of the group. ++ * @return the roster group with the specified name. ++ */ ++ public RosterGroup getGroup(String name) { ++ return groups.get(name); ++ } ++ ++ /** ++ * Returns the number of the groups in the roster. ++ * ++ * @return the number of groups in the roster. ++ */ ++ public int getGroupCount() { ++ return groups.size(); ++ } ++ ++ /** ++ * Returns an unmodifiable collections of all the roster groups. ++ * ++ * @return an iterator for all roster groups. ++ */ ++ public Collection getGroups() { ++ return Collections.unmodifiableCollection(groups.values()); ++ } ++ ++ /** ++ * Returns the presence info for a particular user. If the user is offline, or ++ * if no presence data is available (such as when you are not subscribed to the ++ * user's presence updates), unavailable presence will be returned.

      ++ *

      ++ * If the user has several presences (one for each resource), then the presence with ++ * highest priority will be returned. If multiple presences have the same priority, ++ * the one with the "most available" presence mode will be returned. In order, ++ * that's {@link org.jivesoftware.smack.packet.Presence.Mode#chat free to chat}, ++ * {@link org.jivesoftware.smack.packet.Presence.Mode#available available}, ++ * {@link org.jivesoftware.smack.packet.Presence.Mode#away away}, ++ * {@link org.jivesoftware.smack.packet.Presence.Mode#xa extended away}, and ++ * {@link org.jivesoftware.smack.packet.Presence.Mode#dnd do not disturb}.

      ++ *

      ++ * Note that presence information is received asynchronously. So, just after logging ++ * in to the server, presence values for users in the roster may be unavailable ++ * even if they are actually online. In other words, the value returned by this ++ * method should only be treated as a snapshot in time, and may not accurately reflect ++ * other user's presence instant by instant. If you need to track presence over time, ++ * such as when showing a visual representation of the roster, consider using a ++ * {@link RosterListener}. ++ * ++ * @param user an XMPP ID. The address could be in any valid format (e.g. ++ * "domain/resource", "user@domain" or "user@domain/resource"). Any resource ++ * information that's part of the ID will be discarded. ++ * @return the user's current presence, or unavailable presence if the user is offline ++ * or if no presence information is available.. ++ */ ++ public Presence getPresence(String user) { ++ String key = getPresenceMapKey(StringUtils.parseBareAddress(user)); ++ Map userPresences = presenceMap.get(key); ++ if (userPresences == null) { ++ Presence presence = new Presence(Presence.Type.unavailable); ++ presence.setFrom(user); ++ return presence; ++ } ++ else { ++ // Find the resource with the highest priority ++ // Might be changed to use the resource with the highest availability instead. ++ Presence presence = null; ++ ++ for (String resource : userPresences.keySet()) { ++ Presence p = userPresences.get(resource); ++ if (!p.isAvailable()) { ++ continue; ++ } ++ // Chose presence with highest priority first. ++ if (presence == null || p.getPriority() > presence.getPriority()) { ++ presence = p; ++ } ++ // If equal priority, choose "most available" by the mode value. ++ else if (p.getPriority() == presence.getPriority()) { ++ Presence.Mode pMode = p.getMode(); ++ // Default to presence mode of available. ++ if (pMode == null) { ++ pMode = Presence.Mode.available; ++ } ++ Presence.Mode presenceMode = presence.getMode(); ++ // Default to presence mode of available. ++ if (presenceMode == null) { ++ presenceMode = Presence.Mode.available; ++ } ++ if (pMode.compareTo(presenceMode) < 0) { ++ presence = p; ++ } ++ } ++ } ++ if (presence == null) { ++ presence = new Presence(Presence.Type.unavailable); ++ presence.setFrom(user); ++ return presence; ++ } ++ else { ++ return presence; ++ } ++ } ++ } ++ ++ /** ++ * Returns the presence info for a particular user's resource, or unavailable presence ++ * if the user is offline or if no presence information is available, such as ++ * when you are not subscribed to the user's presence updates. ++ * ++ * @param userWithResource a fully qualified XMPP ID including a resource (user@domain/resource). ++ * @return the user's current presence, or unavailable presence if the user is offline ++ * or if no presence information is available. ++ */ ++ public Presence getPresenceResource(String userWithResource) { ++ String key = getPresenceMapKey(userWithResource); ++ String resource = StringUtils.parseResource(userWithResource); ++ Map userPresences = presenceMap.get(key); ++ if (userPresences == null) { ++ Presence presence = new Presence(Presence.Type.unavailable); ++ presence.setFrom(userWithResource); ++ return presence; ++ } ++ else { ++ Presence presence = userPresences.get(resource); ++ if (presence == null) { ++ presence = new Presence(Presence.Type.unavailable); ++ presence.setFrom(userWithResource); ++ return presence; ++ } ++ else { ++ return presence; ++ } ++ } ++ } ++ ++ /** ++ * Returns an iterator (of Presence objects) for all of a user's current presences ++ * or an unavailable presence if the user is unavailable (offline) or if no presence ++ * information is available, such as when you are not subscribed to the user's presence ++ * updates. ++ * ++ * @param user a XMPP ID, e.g. jdoe@example.com. ++ * @return an iterator (of Presence objects) for all the user's current presences, ++ * or an unavailable presence if the user is offline or if no presence information ++ * is available. ++ */ ++ public Iterator getPresences(String user) { ++ String key = getPresenceMapKey(user); ++ Map userPresences = presenceMap.get(key); ++ if (userPresences == null) { ++ Presence presence = new Presence(Presence.Type.unavailable); ++ presence.setFrom(user); ++ return Arrays.asList(presence).iterator(); ++ } ++ else { ++ Collection answer = new ArrayList(); ++ for (Presence presence : userPresences.values()) { ++ if (presence.isAvailable()) { ++ answer.add(presence); ++ } ++ } ++ if (!answer.isEmpty()) { ++ return answer.iterator(); ++ } ++ else { ++ Presence presence = new Presence(Presence.Type.unavailable); ++ presence.setFrom(user); ++ return Arrays.asList(presence).iterator(); ++ } ++ } ++ } ++ ++ /** ++ * Cleans up all resources used by the roster. ++ */ ++ void cleanup() { ++ rosterListeners.clear(); ++ } ++ ++ /** ++ * Returns the key to use in the presenceMap for a fully qualified XMPP ID. ++ * The roster can contain any valid address format such us "domain/resource", ++ * "user@domain" or "user@domain/resource". If the roster contains an entry ++ * associated with the fully qualified XMPP ID then use the fully qualified XMPP ++ * ID as the key in presenceMap, otherwise use the bare address. Note: When the ++ * key in presenceMap is a fully qualified XMPP ID, the userPresences is useless ++ * since it will always contain one entry for the user. ++ * ++ * @param user the bare or fully qualified XMPP ID, e.g. jdoe@example.com or ++ * jdoe@example.com/Work. ++ * @return the key to use in the presenceMap for the fully qualified XMPP ID. ++ */ ++ private String getPresenceMapKey(String user) { ++ if (user == null) { ++ return null; ++ } ++ String key = user; ++ if (!contains(user)) { ++ key = StringUtils.parseBareAddress(user); ++ } ++ return key.toLowerCase(); ++ } ++ ++ /** ++ * Changes the presence of available contacts offline by simulating an unavailable ++ * presence sent from the server. After a disconnection, every Presence is set ++ * to offline. ++ */ ++ public void setOfflinePresences() { ++ Presence packetUnavailable; ++ for (String user : presenceMap.keySet()) { ++ Map resources = presenceMap.get(user); ++ if (resources != null) { ++ for (String resource : resources.keySet()) { ++ packetUnavailable = new Presence(Presence.Type.unavailable); ++ packetUnavailable.setFrom(user + "/" + resource); ++ presencePacketListener.processPacket(packetUnavailable); ++ } ++ } ++ } ++ } ++ ++ /** ++ * Fires roster changed event to roster listeners indicating that the ++ * specified collections of contacts have been added, updated or deleted ++ * from the roster. ++ * ++ * @param addedEntries the collection of address of the added contacts. ++ * @param updatedEntries the collection of address of the updated contacts. ++ * @param deletedEntries the collection of address of the deleted contacts. ++ */ ++ private void fireRosterChangedEvent(Collection addedEntries, Collection updatedEntries, ++ Collection deletedEntries) { ++ for (RosterListener listener : rosterListeners) { ++ if (!addedEntries.isEmpty()) { ++ listener.entriesAdded(addedEntries); ++ } ++ if (!updatedEntries.isEmpty()) { ++ listener.entriesUpdated(updatedEntries); ++ } ++ if (!deletedEntries.isEmpty()) { ++ listener.entriesDeleted(deletedEntries); ++ } ++ } ++ } ++ ++ /** ++ * Fires roster presence changed event to roster listeners. ++ * ++ * @param presence the presence change. ++ */ ++ private void fireRosterPresenceEvent(Presence presence) { ++ for (RosterListener listener : rosterListeners) { ++ listener.presenceChanged(presence); ++ } ++ } ++ ++ /** ++ * An enumeration for the subscription mode options. ++ */ ++ public enum SubscriptionMode { ++ ++ /** ++ * Automatically accept all subscription and unsubscription requests. This is ++ * the default mode and is suitable for simple client. More complex client will ++ * likely wish to handle subscription requests manually. ++ */ ++ accept_all, ++ ++ /** ++ * Automatically reject all subscription requests. ++ */ ++ reject_all, ++ ++ /** ++ * Subscription requests are ignored, which means they must be manually ++ * processed by registering a listener for presence packets and then looking ++ * for any presence requests that have the type Presence.Type.SUBSCRIBE or ++ * Presence.Type.UNSUBSCRIBE. ++ */ ++ manual ++ } ++ ++ /** ++ * Listens for all presence packets and processes them. ++ */ ++ private class PresencePacketListener implements PacketListener { ++ ++ public void processPacket(Packet packet) { ++ Presence presence = (Presence) packet; ++ String from = presence.getFrom(); ++ String key = getPresenceMapKey(from); ++ ++ // If an "available" presence, add it to the presence map. Each presence ++ // map will hold for a particular user a map with the presence ++ // packets saved for each resource. ++ if (presence.getType() == Presence.Type.available) { ++ Map userPresences; ++ // Get the user presence map ++ if (presenceMap.get(key) == null) { ++ userPresences = new ConcurrentHashMap(); ++ presenceMap.put(key, userPresences); ++ } ++ else { ++ userPresences = presenceMap.get(key); ++ } ++ // See if an offline presence was being stored in the map. If so, remove ++ // it since we now have an online presence. ++ userPresences.remove(""); ++ // Add the new presence, using the resources as a key. ++ userPresences.put(StringUtils.parseResource(from), presence); ++ // If the user is in the roster, fire an event. ++ RosterEntry entry = entries.get(key); ++ if (entry != null) { ++ fireRosterPresenceEvent(presence); ++ } ++ } ++ // If an "unavailable" packet. ++ else if (presence.getType() == Presence.Type.unavailable) { ++ // If no resource, this is likely an offline presence as part of ++ // a roster presence flood. In that case, we store it. ++ if ("".equals(StringUtils.parseResource(from))) { ++ Map userPresences; ++ // Get the user presence map ++ if (presenceMap.get(key) == null) { ++ userPresences = new ConcurrentHashMap(); ++ presenceMap.put(key, userPresences); ++ } ++ else { ++ userPresences = presenceMap.get(key); ++ } ++ userPresences.put("", presence); ++ } ++ // Otherwise, this is a normal offline presence. ++ else if (presenceMap.get(key) != null) { ++ Map userPresences = presenceMap.get(key); ++ // Store the offline presence, as it may include extra information ++ // such as the user being on vacation. ++ userPresences.put(StringUtils.parseResource(from), presence); ++ } ++ // If the user is in the roster, fire an event. ++ RosterEntry entry = entries.get(key); ++ if (entry != null) { ++ fireRosterPresenceEvent(presence); ++ } ++ } ++ else if (presence.getType() == Presence.Type.subscribe) { ++ if (subscriptionMode == SubscriptionMode.accept_all) { ++ // Accept all subscription requests. ++ Presence response = new Presence(Presence.Type.subscribed); ++ response.setTo(presence.getFrom()); ++ connection.sendPacket(response); ++ } ++ else if (subscriptionMode == SubscriptionMode.reject_all) { ++ // Reject all subscription requests. ++ Presence response = new Presence(Presence.Type.unsubscribed); ++ response.setTo(presence.getFrom()); ++ connection.sendPacket(response); ++ } ++ // Otherwise, in manual mode so ignore. ++ } ++ else if (presence.getType() == Presence.Type.unsubscribe) { ++ if (subscriptionMode != SubscriptionMode.manual) { ++ // Acknowledge and accept unsubscription notification so that the ++ // server will stop sending notifications saying that the contact ++ // has unsubscribed to our presence. ++ Presence response = new Presence(Presence.Type.unsubscribed); ++ response.setTo(presence.getFrom()); ++ connection.sendPacket(response); ++ } ++ // Otherwise, in manual mode so ignore. ++ } ++ // Error presence packets from a bare JID mean we invalidate all existing ++ // presence info for the user. ++ else if (presence.getType() == Presence.Type.error && ++ "".equals(StringUtils.parseResource(from))) ++ { ++ Map userPresences; ++ if (!presenceMap.containsKey(key)) { ++ userPresences = new ConcurrentHashMap(); ++ presenceMap.put(key, userPresences); ++ } ++ else { ++ userPresences = presenceMap.get(key); ++ // Any other presence data is invalidated by the error packet. ++ userPresences.clear(); ++ } ++ // Set the new presence using the empty resource as a key. ++ userPresences.put("", presence); ++ // If the user is in the roster, fire an event. ++ RosterEntry entry = entries.get(key); ++ if (entry != null) { ++ fireRosterPresenceEvent(presence); ++ } ++ } ++ } ++ } ++ ++ /** ++ * Listen for empty IQ results which indicate that the client has already a current ++ * roster version ++ * @author Till Klocke ++ * ++ */ ++ ++ private class RosterResultListener implements PacketListener{ ++ ++ public void processPacket(Packet packet) { ++ if(packet instanceof IQ){ ++ IQ result = (IQ)packet; ++ if(result.getType().equals(IQ.Type.RESULT) && result.getExtensions().isEmpty()){ ++ Collection addedEntries = new ArrayList(); ++ Collection updatedEntries = new ArrayList(); ++ Collection deletedEntries = new ArrayList(); ++ if(persistentStorage!=null){ ++ for(RosterPacket.Item item : persistentStorage.getEntries()){ ++ insertRosterItem(item,addedEntries,updatedEntries,deletedEntries); ++ } ++ } ++ synchronized (Roster.this) { ++ rosterInitialized = true; ++ Roster.this.notifyAll(); ++ } ++ fireRosterChangedEvent(addedEntries,updatedEntries,deletedEntries); ++ } ++ } ++ connection.removePacketListener(this); ++ } ++ } ++ ++ /** ++ * Listens for all roster packets and processes them. ++ */ ++ private class RosterPacketListener implements PacketListener { ++ ++ public void processPacket(Packet packet) { ++ // Keep a registry of the entries that were added, deleted or updated. An event ++ // will be fired for each affected entry ++ Collection addedEntries = new ArrayList(); ++ Collection updatedEntries = new ArrayList(); ++ Collection deletedEntries = new ArrayList(); ++ ++ String version=null; ++ RosterPacket rosterPacket = (RosterPacket) packet; ++ List rosterItems = new ArrayList(); ++ for(RosterPacket.Item item : rosterPacket.getRosterItems()){ ++ rosterItems.add(item); ++ } ++ //Here we check if the server send a versioned roster, if not we do not use ++ //the roster storage to store entries and work like in the old times ++ if(rosterPacket.getVersion()==null){ ++ persistentStorage=null; ++ } else{ ++ version = rosterPacket.getVersion(); ++ } ++ ++ if(persistentStorage!=null && !rosterInitialized){ ++ for(RosterPacket.Item item : persistentStorage.getEntries()){ ++ rosterItems.add(item); ++ } ++ } ++ ++ for (RosterPacket.Item item : rosterItems) { ++ insertRosterItem(item,addedEntries,updatedEntries,deletedEntries); ++ } ++ if(persistentStorage!=null){ ++ for (RosterPacket.Item i : rosterPacket.getRosterItems()){ ++ if(i.getItemType().equals(RosterPacket.ItemType.remove)){ ++ persistentStorage.removeEntry(i.getUser()); ++ } ++ else{ ++ persistentStorage.addEntry(i, version); ++ } ++ } ++ } ++ // Mark the roster as initialized. ++ synchronized (Roster.this) { ++ rosterInitialized = true; ++ Roster.this.notifyAll(); ++ } ++ ++ // Fire event for roster listeners. ++ fireRosterChangedEvent(addedEntries, updatedEntries, deletedEntries); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterEntry.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterEntry.java +new file mode 100644 +index 0000000..31f7a1e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterEntry.java +@@ -0,0 +1,239 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.RosterPacket; ++ ++import java.util.*; ++ ++/** ++ * Each user in your roster is represented by a roster entry, which contains the user's ++ * JID and a name or nickname you assign. ++ * ++ * @author Matt Tucker ++ */ ++public class RosterEntry { ++ ++ private String user; ++ private String name; ++ private RosterPacket.ItemType type; ++ private RosterPacket.ItemStatus status; ++ final private Roster roster; ++ final private Connection connection; ++ ++ /** ++ * Creates a new roster entry. ++ * ++ * @param user the user. ++ * @param name the nickname for the entry. ++ * @param type the subscription type. ++ * @param status the subscription status (related to subscriptions pending to be approbed). ++ * @param connection a connection to the XMPP server. ++ */ ++ RosterEntry(String user, String name, RosterPacket.ItemType type, ++ RosterPacket.ItemStatus status, Roster roster, Connection connection) { ++ this.user = user; ++ this.name = name; ++ this.type = type; ++ this.status = status; ++ this.roster = roster; ++ this.connection = connection; ++ } ++ ++ /** ++ * Returns the JID of the user associated with this entry. ++ * ++ * @return the user associated with this entry. ++ */ ++ public String getUser() { ++ return user; ++ } ++ ++ /** ++ * Returns the name associated with this entry. ++ * ++ * @return the name. ++ */ ++ public String getName() { ++ return name; ++ } ++ ++ /** ++ * Sets the name associated with this entry. ++ * ++ * @param name the name. ++ */ ++ public void setName(String name) { ++ // Do nothing if the name hasn't changed. ++ if (name != null && name.equals(this.name)) { ++ return; ++ } ++ this.name = name; ++ RosterPacket packet = new RosterPacket(); ++ packet.setType(IQ.Type.SET); ++ packet.addRosterItem(toRosterItem(this)); ++ connection.sendPacket(packet); ++ } ++ ++ /** ++ * Updates the state of the entry with the new values. ++ * ++ * @param name the nickname for the entry. ++ * @param type the subscription type. ++ * @param status the subscription status (related to subscriptions pending to be approbed). ++ */ ++ void updateState(String name, RosterPacket.ItemType type, RosterPacket.ItemStatus status) { ++ this.name = name; ++ this.type = type; ++ this.status = status; ++ } ++ ++ /** ++ * Returns an unmodifiable collection of the roster groups that this entry belongs to. ++ * ++ * @return an iterator for the groups this entry belongs to. ++ */ ++ public Collection getGroups() { ++ List results = new ArrayList(); ++ // Loop through all roster groups and find the ones that contain this ++ // entry. This algorithm should be fine ++ for (RosterGroup group: roster.getGroups()) { ++ if (group.contains(this)) { ++ results.add(group); ++ } ++ } ++ return Collections.unmodifiableCollection(results); ++ } ++ ++ /** ++ * Returns the roster subscription type of the entry. When the type is ++ * RosterPacket.ItemType.none or RosterPacket.ItemType.from, ++ * refer to {@link RosterEntry getStatus()} to see if a subscription request ++ * is pending. ++ * ++ * @return the type. ++ */ ++ public RosterPacket.ItemType getType() { ++ return type; ++ } ++ ++ /** ++ * Returns the roster subscription status of the entry. When the status is ++ * RosterPacket.ItemStatus.SUBSCRIPTION_PENDING, the contact has to answer the ++ * subscription request. ++ * ++ * @return the status. ++ */ ++ public RosterPacket.ItemStatus getStatus() { ++ return status; ++ } ++ ++ public String toString() { ++ StringBuilder buf = new StringBuilder(); ++ if (name != null) { ++ buf.append(name).append(": "); ++ } ++ buf.append(user); ++ Collection groups = getGroups(); ++ if (!groups.isEmpty()) { ++ buf.append(" ["); ++ Iterator iter = groups.iterator(); ++ RosterGroup group = iter.next(); ++ buf.append(group.getName()); ++ while (iter.hasNext()) { ++ buf.append(", "); ++ group = iter.next(); ++ buf.append(group.getName()); ++ } ++ buf.append("]"); ++ } ++ return buf.toString(); ++ } ++ ++ public boolean equals(Object object) { ++ if (this == object) { ++ return true; ++ } ++ if (object != null && object instanceof RosterEntry) { ++ return user.equals(((RosterEntry)object).getUser()); ++ } ++ else { ++ return false; ++ } ++ } ++ ++ /** ++ * Indicates whether some other object is "equal to" this by comparing all members. ++ *

      ++ * The {@link #equals(Object)} method returns true if the user JIDs are equal. ++ * ++ * @param obj the reference object with which to compare. ++ * @return true if this object is the same as the obj argument; false ++ * otherwise. ++ */ ++ public boolean equalsDeep(Object obj) { ++ if (this == obj) ++ return true; ++ if (obj == null) ++ return false; ++ if (getClass() != obj.getClass()) ++ return false; ++ RosterEntry other = (RosterEntry) obj; ++ if (name == null) { ++ if (other.name != null) ++ return false; ++ } ++ else if (!name.equals(other.name)) ++ return false; ++ if (status == null) { ++ if (other.status != null) ++ return false; ++ } ++ else if (!status.equals(other.status)) ++ return false; ++ if (type == null) { ++ if (other.type != null) ++ return false; ++ } ++ else if (!type.equals(other.type)) ++ return false; ++ if (user == null) { ++ if (other.user != null) ++ return false; ++ } ++ else if (!user.equals(other.user)) ++ return false; ++ return true; ++ } ++ ++ static RosterPacket.Item toRosterItem(RosterEntry entry) { ++ RosterPacket.Item item = new RosterPacket.Item(entry.getUser(), entry.getName()); ++ item.setItemType(entry.getType()); ++ item.setItemStatus(entry.getStatus()); ++ // Set the correct group names for the item. ++ for (RosterGroup group : entry.getGroups()) { ++ item.addGroupName(group.getName()); ++ } ++ return item; ++ } ++ ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterGroup.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterGroup.java +new file mode 100644 +index 0000000..e768f6d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterGroup.java +@@ -0,0 +1,253 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.RosterPacket; ++import org.jivesoftware.smack.util.StringUtils; ++ ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.List; ++ ++/** ++ * A group of roster entries. ++ * ++ * @see Roster#getGroup(String) ++ * @author Matt Tucker ++ */ ++public class RosterGroup { ++ ++ private String name; ++ private Connection connection; ++ private final List entries; ++ ++ /** ++ * Creates a new roster group instance. ++ * ++ * @param name the name of the group. ++ * @param connection the connection the group belongs to. ++ */ ++ RosterGroup(String name, Connection connection) { ++ this.name = name; ++ this.connection = connection; ++ entries = new ArrayList(); ++ } ++ ++ /** ++ * Returns the name of the group. ++ * ++ * @return the name of the group. ++ */ ++ public String getName() { ++ return name; ++ } ++ ++ /** ++ * Sets the name of the group. Changing the group's name is like moving all the group entries ++ * of the group to a new group specified by the new name. Since this group won't have entries ++ * it will be removed from the roster. This means that all the references to this object will ++ * be invalid and will need to be updated to the new group specified by the new name. ++ * ++ * @param name the name of the group. ++ */ ++ public void setName(String name) { ++ synchronized (entries) { ++ for (RosterEntry entry : entries) { ++ RosterPacket packet = new RosterPacket(); ++ packet.setType(IQ.Type.SET); ++ RosterPacket.Item item = RosterEntry.toRosterItem(entry); ++ item.removeGroupName(this.name); ++ item.addGroupName(name); ++ packet.addRosterItem(item); ++ connection.sendPacket(packet); ++ } ++ } ++ } ++ ++ /** ++ * Returns the number of entries in the group. ++ * ++ * @return the number of entries in the group. ++ */ ++ public int getEntryCount() { ++ synchronized (entries) { ++ return entries.size(); ++ } ++ } ++ ++ /** ++ * Returns an unmodifiable collection of all entries in the group. ++ * ++ * @return all entries in the group. ++ */ ++ public Collection getEntries() { ++ synchronized (entries) { ++ return Collections.unmodifiableList(new ArrayList(entries)); ++ } ++ } ++ ++ /** ++ * Returns the roster entry associated with the given XMPP address or ++ * null if the user is not an entry in the group. ++ * ++ * @param user the XMPP address of the user (eg "jsmith@example.com"). ++ * @return the roster entry or null if it does not exist in the group. ++ */ ++ public RosterEntry getEntry(String user) { ++ if (user == null) { ++ return null; ++ } ++ // Roster entries never include a resource so remove the resource ++ // if it's a part of the XMPP address. ++ user = StringUtils.parseBareAddress(user); ++ String userLowerCase = user.toLowerCase(); ++ synchronized (entries) { ++ for (RosterEntry entry : entries) { ++ if (entry.getUser().equals(userLowerCase)) { ++ return entry; ++ } ++ } ++ } ++ return null; ++ } ++ ++ /** ++ * Returns true if the specified entry is part of this group. ++ * ++ * @param entry a roster entry. ++ * @return true if the entry is part of this group. ++ */ ++ public boolean contains(RosterEntry entry) { ++ synchronized (entries) { ++ return entries.contains(entry); ++ } ++ } ++ ++ /** ++ * Returns true if the specified XMPP address is an entry in this group. ++ * ++ * @param user the XMPP address of the user. ++ * @return true if the XMPP address is an entry in this group. ++ */ ++ public boolean contains(String user) { ++ return getEntry(user) != null; ++ } ++ ++ /** ++ * Adds a roster entry to this group. If the entry was unfiled then it will be removed from ++ * the unfiled list and will be added to this group. ++ * Note that this is an asynchronous call -- Smack must wait for the server ++ * to receive the updated roster. ++ * ++ * @param entry a roster entry. ++ * @throws XMPPException if an error occured while trying to add the entry to the group. ++ */ ++ public void addEntry(RosterEntry entry) throws XMPPException { ++ PacketCollector collector = null; ++ // Only add the entry if it isn't already in the list. ++ synchronized (entries) { ++ if (!entries.contains(entry)) { ++ RosterPacket packet = new RosterPacket(); ++ packet.setType(IQ.Type.SET); ++ RosterPacket.Item item = RosterEntry.toRosterItem(entry); ++ item.addGroupName(getName()); ++ packet.addRosterItem(item); ++ // Wait up to a certain number of seconds for a reply from the server. ++ collector = connection ++ .createPacketCollector(new PacketIDFilter(packet.getPacketID())); ++ connection.sendPacket(packet); ++ } ++ } ++ if (collector != null) { ++ IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ // If the server replied with an error, throw an exception. ++ else if (response.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(response.getError()); ++ } ++ } ++ } ++ ++ /** ++ * Removes a roster entry from this group. If the entry does not belong to any other group ++ * then it will be considered as unfiled, therefore it will be added to the list of unfiled ++ * entries. ++ * Note that this is an asynchronous call -- Smack must wait for the server ++ * to receive the updated roster. ++ * ++ * @param entry a roster entry. ++ * @throws XMPPException if an error occured while trying to remove the entry from the group. ++ */ ++ public void removeEntry(RosterEntry entry) throws XMPPException { ++ PacketCollector collector = null; ++ // Only remove the entry if it's in the entry list. ++ // Remove the entry locally, if we wait for RosterPacketListenerprocess>>Packet(Packet) ++ // to take place the entry will exist in the group until a packet is received from the ++ // server. ++ synchronized (entries) { ++ if (entries.contains(entry)) { ++ RosterPacket packet = new RosterPacket(); ++ packet.setType(IQ.Type.SET); ++ RosterPacket.Item item = RosterEntry.toRosterItem(entry); ++ item.removeGroupName(this.getName()); ++ packet.addRosterItem(item); ++ // Wait up to a certain number of seconds for a reply from the server. ++ collector = connection ++ .createPacketCollector(new PacketIDFilter(packet.getPacketID())); ++ connection.sendPacket(packet); ++ } ++ } ++ if (collector != null) { ++ IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ // If the server replied with an error, throw an exception. ++ else if (response.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(response.getError()); ++ } ++ } ++ } ++ ++ public void addEntryLocal(RosterEntry entry) { ++ // Only add the entry if it isn't already in the list. ++ synchronized (entries) { ++ entries.remove(entry); ++ entries.add(entry); ++ } ++ } ++ ++ void removeEntryLocal(RosterEntry entry) { ++ // Only remove the entry if it's in the entry list. ++ synchronized (entries) { ++ if (entries.contains(entry)) { ++ entries.remove(entry); ++ } ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterListener.java +new file mode 100644 +index 0000000..8be9ddc +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterListener.java +@@ -0,0 +1,83 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.packet.Presence; ++ ++import java.util.Collection; ++ ++/** ++ * A listener that is fired any time a roster is changed or the presence of ++ * a user in the roster is changed. ++ * ++ * @see Roster#addRosterListener(RosterListener) ++ * @author Matt Tucker ++ */ ++public interface RosterListener { ++ ++ /** ++ * Called when roster entries are added. ++ * ++ * @param addresses the XMPP addresses of the contacts that have been added to the roster. ++ */ ++ public void entriesAdded(Collection addresses); ++ ++ /** ++ * Called when a roster entries are updated. ++ * ++ * @param addresses the XMPP addresses of the contacts whose entries have been updated. ++ */ ++ public void entriesUpdated(Collection addresses); ++ ++ /** ++ * Called when a roster entries are removed. ++ * ++ * @param addresses the XMPP addresses of the contacts that have been removed from the roster. ++ */ ++ public void entriesDeleted(Collection addresses); ++ ++ /** ++ * Called when the presence of a roster entry is changed. Care should be taken ++ * when using the presence data delivered as part of this event. Specifically, ++ * when a user account is online with multiple resources, the UI should account ++ * for that. For example, say a user is online with their desktop computer and ++ * mobile phone. If the user logs out of the IM client on their mobile phone, the ++ * user should not be shown in the roster (contact list) as offline since they're ++ * still available as another resource.

      ++ * ++ * To get the current "best presence" for a user after the presence update, query the roster: ++ *

      ++     *    String user = presence.getFrom();
      ++     *    Presence bestPresence = roster.getPresence(user);
      ++     * 
      ++ * ++ * That will return the presence value for the user with the highest priority and ++ * availability. ++ * ++ * Note that this listener is triggered for presence (mode) changes only ++ * (e.g presence of types available and unavailable. Subscription-related ++ * presence packets will not cause this method to be called. ++ * ++ * @param presence the presence that changed. ++ * @see Roster#getPresence(String) ++ */ ++ public void presenceChanged(Presence presence); ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterStorage.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterStorage.java +new file mode 100644 +index 0000000..8c5f386 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterStorage.java +@@ -0,0 +1,54 @@ ++package org.jivesoftware.smack; ++ ++import java.util.List; ++ ++import org.jivesoftware.smack.packet.RosterPacket; ++ ++/** ++ * This is an interface for persistent roster storage needed to implement XEP-0237 ++ * @author Till Klocke ++ * ++ */ ++ ++public interface RosterStorage { ++ ++ /** ++ * This method returns a List object with all RosterEntries contained in this store. ++ * @return List object with all entries in local roster storage ++ */ ++ public List getEntries(); ++ /** ++ * This method returns the RosterEntry which belongs to a specific user. ++ * @param bareJid The bare JID of the RosterEntry ++ * @return The RosterEntry which belongs to that user ++ */ ++ public RosterPacket.Item getEntry(String bareJid); ++ /** ++ * Returns the number of entries in this roster store ++ * @return the number of entries ++ */ ++ public int getEntryCount(); ++ /** ++ * This methos returns the version number as specified by the "ver" attribute ++ * of the local store. Should return an emtpy string if store is empty. ++ * @return local roster version ++ */ ++ public String getRosterVersion(); ++ /** ++ * This method stores a new RosterEntry in this store or overrides an existing one. ++ * If ver is null an IllegalArgumentException should be thrown. ++ * @param entry the entry to save ++ * @param ver the version this roster push contained ++ */ ++ public void addEntry(RosterPacket.Item item, String ver); ++ /** ++ * Removes an entry from the persistent storage ++ * @param bareJid The bare JID of the entry to be removed ++ */ ++ public void removeEntry(String bareJid); ++ /** ++ * Update an entry which has been modified locally ++ * @param entry the entry to be updated ++ */ ++ public void updateLocalEntry(RosterPacket.Item item); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/SASLAuthentication.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/SASLAuthentication.java +new file mode 100644 +index 0000000..f9f963a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/SASLAuthentication.java +@@ -0,0 +1,590 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.packet.Bind; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.Session; ++import org.jivesoftware.smack.sasl.*; ++ ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++import java.io.IOException; ++import java.lang.reflect.Constructor; ++import java.util.*; ++ ++/** ++ *

      This class is responsible authenticating the user using SASL, binding the resource ++ * to the connection and establishing a session with the server.

      ++ * ++ *

      Once TLS has been negotiated (i.e. the connection has been secured) it is possible to ++ * register with the server, authenticate using Non-SASL or authenticate using SASL. If the ++ * server supports SASL then Smack will first try to authenticate using SASL. But if that ++ * fails then Non-SASL will be tried.

      ++ * ++ *

      The server may support many SASL mechanisms to use for authenticating. Out of the box ++ * Smack provides several SASL mechanisms, but it is possible to register new SASL Mechanisms. Use ++ * {@link #registerSASLMechanism(String, Class)} to register a new mechanisms. A registered ++ * mechanism wont be used until {@link #supportSASLMechanism(String, int)} is called. By default, ++ * the list of supported SASL mechanisms is determined from the {@link SmackConfiguration}.

      ++ * ++ *

      Once the user has been authenticated with SASL, it is necessary to bind a resource for ++ * the connection. If no resource is passed in {@link #authenticate(String, String, String)} ++ * then the server will assign a resource for the connection. In case a resource is passed ++ * then the server will receive the desired resource but may assign a modified resource for ++ * the connection.

      ++ * ++ *

      Once a resource has been binded and if the server supports sessions then Smack will establish ++ * a session so that instant messaging and presence functionalities may be used.

      ++ * ++ * @see org.jivesoftware.smack.sasl.SASLMechanism ++ * ++ * @author Gaston Dombiak ++ * @author Jay Kline ++ */ ++public class SASLAuthentication implements UserAuthentication { ++ ++ private static Map implementedMechanisms = new HashMap(); ++ private static List mechanismsPreferences = new ArrayList(); ++ ++ private Connection connection; ++ private Collection serverMechanisms = new ArrayList(); ++ private SASLMechanism currentMechanism = null; ++ /** ++ * Boolean indicating if SASL negotiation has finished and was successful. ++ */ ++ private boolean saslNegotiated; ++ /** ++ * Boolean indication if SASL authentication has failed. When failed the server may end ++ * the connection. ++ */ ++ private boolean saslFailed; ++ private boolean resourceBinded; ++ private boolean sessionSupported; ++ /** ++ * The SASL related error condition if there was one provided by the server. ++ */ ++ private String errorCondition; ++ ++ static { ++ ++ // Register SASL mechanisms supported by Smack ++ registerSASLMechanism("EXTERNAL", SASLExternalMechanism.class); ++ registerSASLMechanism("GSSAPI", SASLGSSAPIMechanism.class); ++ registerSASLMechanism("DIGEST-MD5", SASLDigestMD5Mechanism.class); ++ registerSASLMechanism("CRAM-MD5", SASLCramMD5Mechanism.class); ++ registerSASLMechanism("PLAIN", SASLPlainMechanism.class); ++ registerSASLMechanism("ANONYMOUS", SASLAnonymous.class); ++ ++// supportSASLMechanism("GSSAPI",0); ++ supportSASLMechanism("DIGEST-MD5",0); ++// supportSASLMechanism("CRAM-MD5",2); ++ supportSASLMechanism("PLAIN",1); ++ supportSASLMechanism("ANONYMOUS",2); ++ ++ } ++ ++ /** ++ * Registers a new SASL mechanism ++ * ++ * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. ++ * @param mClass a SASLMechanism subclass. ++ */ ++ public static void registerSASLMechanism(String name, Class mClass) { ++ implementedMechanisms.put(name, mClass); ++ } ++ ++ /** ++ * Unregisters an existing SASL mechanism. Once the mechanism has been unregistered it won't ++ * be possible to authenticate users using the removed SASL mechanism. It also removes the ++ * mechanism from the supported list. ++ * ++ * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. ++ */ ++ public static void unregisterSASLMechanism(String name) { ++ implementedMechanisms.remove(name); ++ mechanismsPreferences.remove(name); ++ } ++ ++ ++ /** ++ * Registers a new SASL mechanism in the specified preference position. The client will try ++ * to authenticate using the most prefered SASL mechanism that is also supported by the server. ++ * The SASL mechanism must be registered via {@link #registerSASLMechanism(String, Class)} ++ * ++ * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. ++ */ ++ public static void supportSASLMechanism(String name) { ++ mechanismsPreferences.add(0, name); ++ } ++ ++ /** ++ * Registers a new SASL mechanism in the specified preference position. The client will try ++ * to authenticate using the most prefered SASL mechanism that is also supported by the server. ++ * Use the index parameter to set the level of preference of the new SASL mechanism. ++ * A value of 0 means that the mechanism is the most prefered one. The SASL mechanism must be ++ * registered via {@link #registerSASLMechanism(String, Class)} ++ * ++ * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. ++ * @param index preference position amongst all the implemented SASL mechanism. Starts with 0. ++ */ ++ public static void supportSASLMechanism(String name, int index) { ++ mechanismsPreferences.add(index, name); ++ } ++ ++ /** ++ * Un-supports an existing SASL mechanism. Once the mechanism has been unregistered it won't ++ * be possible to authenticate users using the removed SASL mechanism. Note that the mechanism ++ * is still registered, but will just not be used. ++ * ++ * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. ++ */ ++ public static void unsupportSASLMechanism(String name) { ++ mechanismsPreferences.remove(name); ++ } ++ ++ /** ++ * Returns the registerd SASLMechanism classes sorted by the level of preference. ++ * ++ * @return the registerd SASLMechanism classes sorted by the level of preference. ++ */ ++ public static List getRegisterSASLMechanisms() { ++ List answer = new ArrayList(); ++ for (String mechanismsPreference : mechanismsPreferences) { ++ answer.add(implementedMechanisms.get(mechanismsPreference)); ++ } ++ return answer; ++ } ++ ++ SASLAuthentication(Connection connection) { ++ super(); ++ this.connection = connection; ++ this.init(); ++ } ++ ++ /** ++ * Returns true if the server offered ANONYMOUS SASL as a way to authenticate users. ++ * ++ * @return true if the server offered ANONYMOUS SASL as a way to authenticate users. ++ */ ++ public boolean hasAnonymousAuthentication() { ++ return serverMechanisms.contains("ANONYMOUS"); ++ } ++ ++ /** ++ * Returns true if the server offered SASL authentication besides ANONYMOUS SASL. ++ * ++ * @return true if the server offered SASL authentication besides ANONYMOUS SASL. ++ */ ++ public boolean hasNonAnonymousAuthentication() { ++ return !serverMechanisms.isEmpty() && (serverMechanisms.size() != 1 || !hasAnonymousAuthentication()); ++ } ++ ++ /** ++ * Performs SASL authentication of the specified user. If SASL authentication was successful ++ * then resource binding and session establishment will be performed. This method will return ++ * the full JID provided by the server while binding a resource to the connection.

      ++ * ++ * The server may assign a full JID with a username or resource different than the requested ++ * by this method. ++ * ++ * @param username the username that is authenticating with the server. ++ * @param resource the desired resource. ++ * @param cbh the CallbackHandler used to get information from the user ++ * @return the full JID provided by the server while binding a resource to the connection. ++ * @throws XMPPException if an error occures while authenticating. ++ */ ++ public String authenticate(String username, String resource, CallbackHandler cbh) ++ throws XMPPException { ++ // Locate the SASLMechanism to use ++ String selectedMechanism = null; ++ for (String mechanism : mechanismsPreferences) { ++ if (implementedMechanisms.containsKey(mechanism) && ++ serverMechanisms.contains(mechanism)) { ++ selectedMechanism = mechanism; ++ break; ++ } ++ } ++ if (selectedMechanism != null) { ++ // A SASL mechanism was found. Authenticate using the selected mechanism and then ++ // proceed to bind a resource ++ try { ++ Class mechanismClass = implementedMechanisms.get(selectedMechanism); ++ Constructor constructor = mechanismClass.getConstructor(SASLAuthentication.class); ++ currentMechanism = (SASLMechanism) constructor.newInstance(this); ++ // Trigger SASL authentication with the selected mechanism. We use ++ // connection.getHost() since GSAPI requires the FQDN of the server, which ++ // may not match the XMPP domain. ++ currentMechanism.authenticate(username, connection.getHost(), cbh); ++ ++ // Wait until SASL negotiation finishes ++ synchronized (this) { ++ if (!saslNegotiated && !saslFailed) { ++ try { ++ wait(30000); ++ } ++ catch (InterruptedException e) { ++ // Ignore ++ } ++ } ++ } ++ ++ if (saslFailed) { ++ // SASL authentication failed and the server may have closed the connection ++ // so throw an exception ++ if (errorCondition != null) { ++ throw new XMPPException("SASL authentication " + ++ selectedMechanism + " failed: " + errorCondition); ++ } ++ else { ++ throw new XMPPException("SASL authentication failed using mechanism " + ++ selectedMechanism); ++ } ++ } ++ ++ if (saslNegotiated) { ++ // Bind a resource for this connection and ++ return bindResourceAndEstablishSession(resource); ++ } else { ++ // SASL authentication failed ++ } ++ } ++ catch (XMPPException e) { ++ throw e; ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } ++ else { ++ throw new XMPPException("SASL Authentication failed. No known authentication mechanisims."); ++ } ++ throw new XMPPException("SASL authentication failed"); ++ } ++ ++ /** ++ * Performs SASL authentication of the specified user. If SASL authentication was successful ++ * then resource binding and session establishment will be performed. This method will return ++ * the full JID provided by the server while binding a resource to the connection.

      ++ * ++ * The server may assign a full JID with a username or resource different than the requested ++ * by this method. ++ * ++ * @param username the username that is authenticating with the server. ++ * @param password the password to send to the server. ++ * @param resource the desired resource. ++ * @return the full JID provided by the server while binding a resource to the connection. ++ * @throws XMPPException if an error occures while authenticating. ++ */ ++ public String authenticate(String username, String password, String resource) ++ throws XMPPException { ++ // Locate the SASLMechanism to use ++ String selectedMechanism = null; ++ for (String mechanism : mechanismsPreferences) { ++ if (implementedMechanisms.containsKey(mechanism) && ++ serverMechanisms.contains(mechanism)) { ++ selectedMechanism = mechanism; ++ break; ++ } ++ } ++ if (selectedMechanism != null) { ++ // A SASL mechanism was found. Authenticate using the selected mechanism and then ++ // proceed to bind a resource ++ try { ++ Class mechanismClass = implementedMechanisms.get(selectedMechanism); ++ Constructor constructor = mechanismClass.getConstructor(SASLAuthentication.class); ++ currentMechanism = (SASLMechanism) constructor.newInstance(this); ++ // Trigger SASL authentication with the selected mechanism. We use ++ // connection.getHost() since GSAPI requires the FQDN of the server, which ++ // may not match the XMPP domain. ++ currentMechanism.authenticate(username, connection.getServiceName(), password); ++ ++ // Wait until SASL negotiation finishes ++ synchronized (this) { ++ if (!saslNegotiated && !saslFailed) { ++ try { ++ wait(30000); ++ } ++ catch (InterruptedException e) { ++ // Ignore ++ } ++ } ++ } ++ ++ if (saslFailed) { ++ // SASL authentication failed and the server may have closed the connection ++ // so throw an exception ++ if (errorCondition != null) { ++ throw new XMPPException("SASL authentication " + ++ selectedMechanism + " failed: " + errorCondition); ++ } ++ else { ++ throw new XMPPException("SASL authentication failed using mechanism " + ++ selectedMechanism); ++ } ++ } ++ ++ if (saslNegotiated) { ++ // Bind a resource for this connection and ++ return bindResourceAndEstablishSession(resource); ++ } ++ else { ++ // SASL authentication failed so try a Non-SASL authentication ++ return new NonSASLAuthentication(connection) ++ .authenticate(username, password, resource); ++ } ++ } ++ catch (XMPPException e) { ++ throw e; ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ // SASL authentication failed so try a Non-SASL authentication ++ return new NonSASLAuthentication(connection) ++ .authenticate(username, password, resource); ++ } ++ } ++ else { ++ // No SASL method was found so try a Non-SASL authentication ++ return new NonSASLAuthentication(connection).authenticate(username, password, resource); ++ } ++ } ++ ++ /** ++ * Performs ANONYMOUS SASL authentication. If SASL authentication was successful ++ * then resource binding and session establishment will be performed. This method will return ++ * the full JID provided by the server while binding a resource to the connection.

      ++ * ++ * The server will assign a full JID with a randomly generated resource and possibly with ++ * no username. ++ * ++ * @return the full JID provided by the server while binding a resource to the connection. ++ * @throws XMPPException if an error occures while authenticating. ++ */ ++ public String authenticateAnonymously() throws XMPPException { ++ try { ++ currentMechanism = new SASLAnonymous(this); ++ currentMechanism.authenticate(null,null,""); ++ ++ // Wait until SASL negotiation finishes ++ synchronized (this) { ++ if (!saslNegotiated && !saslFailed) { ++ try { ++ wait(5000); ++ } ++ catch (InterruptedException e) { ++ // Ignore ++ } ++ } ++ } ++ ++ if (saslFailed) { ++ // SASL authentication failed and the server may have closed the connection ++ // so throw an exception ++ if (errorCondition != null) { ++ throw new XMPPException("SASL authentication failed: " + errorCondition); ++ } ++ else { ++ throw new XMPPException("SASL authentication failed"); ++ } ++ } ++ ++ if (saslNegotiated) { ++ // Bind a resource for this connection and ++ return bindResourceAndEstablishSession(null); ++ } ++ else { ++ return new NonSASLAuthentication(connection).authenticateAnonymously(); ++ } ++ } catch (IOException e) { ++ return new NonSASLAuthentication(connection).authenticateAnonymously(); ++ } ++ } ++ ++ private String bindResourceAndEstablishSession(String resource) throws XMPPException { ++ // Wait until server sends response containing the element ++ synchronized (this) { ++ if (!resourceBinded) { ++ try { ++ wait(30000); ++ } ++ catch (InterruptedException e) { ++ // Ignore ++ } ++ } ++ } ++ ++ if (!resourceBinded) { ++ // Server never offered resource binding ++ throw new XMPPException("Resource binding not offered by server"); ++ } ++ ++ Bind bindResource = new Bind(); ++ bindResource.setResource(resource); ++ ++ PacketCollector collector = connection ++ .createPacketCollector(new PacketIDFilter(bindResource.getPacketID())); ++ // Send the packet ++ connection.sendPacket(bindResource); ++ // Wait up to a certain number of seconds for a response from the server. ++ Bind response = (Bind) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ // If the server replied with an error, throw an exception. ++ else if (response.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(response.getError()); ++ } ++ String userJID = response.getJid(); ++ ++ if (sessionSupported) { ++ Session session = new Session(); ++ collector = connection.createPacketCollector(new PacketIDFilter(session.getPacketID())); ++ // Send the packet ++ connection.sendPacket(session); ++ // Wait up to a certain number of seconds for a response from the server. ++ IQ ack = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ collector.cancel(); ++ if (ack == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ // If the server replied with an error, throw an exception. ++ else if (ack.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(ack.getError()); ++ } ++ } ++ else { ++ // Server never offered session establishment ++ throw new XMPPException("Session establishment not offered by server"); ++ } ++ return userJID; ++ } ++ ++ /** ++ * Sets the available SASL mechanism reported by the server. The server will report the ++ * available SASL mechanism once the TLS negotiation was successful. This information is ++ * stored and will be used when doing the authentication for logging in the user. ++ * ++ * @param mechanisms collection of strings with the available SASL mechanism reported ++ * by the server. ++ */ ++ void setAvailableSASLMethods(Collection mechanisms) { ++ this.serverMechanisms = mechanisms; ++ } ++ ++ /** ++ * Returns true if the user was able to authenticate with the server usins SASL. ++ * ++ * @return true if the user was able to authenticate with the server usins SASL. ++ */ ++ public boolean isAuthenticated() { ++ return saslNegotiated; ++ } ++ ++ /** ++ * The server is challenging the SASL authentication we just sent. Forward the challenge ++ * to the current SASLMechanism we are using. The SASLMechanism will send a response to ++ * the server. The length of the challenge-response sequence varies according to the ++ * SASLMechanism in use. ++ * ++ * @param challenge a base64 encoded string representing the challenge. ++ * @throws IOException If a network error occures while authenticating. ++ */ ++ void challengeReceived(String challenge) throws IOException { ++ currentMechanism.challengeReceived(challenge); ++ } ++ ++ /** ++ * Notification message saying that SASL authentication was successful. The next step ++ * would be to bind the resource. ++ */ ++ void authenticated() { ++ synchronized (this) { ++ saslNegotiated = true; ++ // Wake up the thread that is waiting in the #authenticate method ++ notify(); ++ } ++ } ++ ++ /** ++ * Notification message saying that SASL authentication has failed. The server may have ++ * closed the connection depending on the number of possible retries. ++ * ++ * @deprecated replaced by {@see #authenticationFailed(String)}. ++ */ ++ void authenticationFailed() { ++ authenticationFailed(null); ++ } ++ ++ /** ++ * Notification message saying that SASL authentication has failed. The server may have ++ * closed the connection depending on the number of possible retries. ++ * ++ * @param condition the error condition provided by the server. ++ */ ++ void authenticationFailed(String condition) { ++ synchronized (this) { ++ saslFailed = true; ++ errorCondition = condition; ++ // Wake up the thread that is waiting in the #authenticate method ++ notify(); ++ } ++ } ++ ++ /** ++ * Notification message saying that the server requires the client to bind a ++ * resource to the stream. ++ */ ++ void bindingRequired() { ++ synchronized (this) { ++ resourceBinded = true; ++ // Wake up the thread that is waiting in the #authenticate method ++ notify(); ++ } ++ } ++ ++ public void send(Packet stanza) { ++ connection.sendPacket(stanza); ++ } ++ ++ /** ++ * Notification message saying that the server supports sessions. When a server supports ++ * sessions the client needs to send a Session packet after successfully binding a resource ++ * for the session. ++ */ ++ void sessionsSupported() { ++ sessionSupported = true; ++ } ++ ++ /** ++ * Initializes the internal state in order to be able to be reused. The authentication ++ * is used by the connection at the first login and then reused after the connection ++ * is disconnected and then reconnected. ++ */ ++ protected void init() { ++ saslNegotiated = false; ++ saslFailed = false; ++ resourceBinded = false; ++ sessionSupported = false; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/SASLAuthentication.java.orig b/external/asmack/build/src/trunk/org/jivesoftware/smack/SASLAuthentication.java.orig +new file mode 100644 +index 0000000..73995d2 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/SASLAuthentication.java.orig +@@ -0,0 +1,590 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.packet.Bind; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.Session; ++import org.jivesoftware.smack.sasl.*; ++ ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++import java.io.IOException; ++import java.lang.reflect.Constructor; ++import java.util.*; ++ ++/** ++ *

      This class is responsible authenticating the user using SASL, binding the resource ++ * to the connection and establishing a session with the server.

      ++ * ++ *

      Once TLS has been negotiated (i.e. the connection has been secured) it is possible to ++ * register with the server, authenticate using Non-SASL or authenticate using SASL. If the ++ * server supports SASL then Smack will first try to authenticate using SASL. But if that ++ * fails then Non-SASL will be tried.

      ++ * ++ *

      The server may support many SASL mechanisms to use for authenticating. Out of the box ++ * Smack provides several SASL mechanisms, but it is possible to register new SASL Mechanisms. Use ++ * {@link #registerSASLMechanism(String, Class)} to register a new mechanisms. A registered ++ * mechanism wont be used until {@link #supportSASLMechanism(String, int)} is called. By default, ++ * the list of supported SASL mechanisms is determined from the {@link SmackConfiguration}.

      ++ * ++ *

      Once the user has been authenticated with SASL, it is necessary to bind a resource for ++ * the connection. If no resource is passed in {@link #authenticate(String, String, String)} ++ * then the server will assign a resource for the connection. In case a resource is passed ++ * then the server will receive the desired resource but may assign a modified resource for ++ * the connection.

      ++ * ++ *

      Once a resource has been binded and if the server supports sessions then Smack will establish ++ * a session so that instant messaging and presence functionalities may be used.

      ++ * ++ * @see org.jivesoftware.smack.sasl.SASLMechanism ++ * ++ * @author Gaston Dombiak ++ * @author Jay Kline ++ */ ++public class SASLAuthentication implements UserAuthentication { ++ ++ private static Map implementedMechanisms = new HashMap(); ++ private static List mechanismsPreferences = new ArrayList(); ++ ++ private Connection connection; ++ private Collection serverMechanisms = new ArrayList(); ++ private SASLMechanism currentMechanism = null; ++ /** ++ * Boolean indicating if SASL negotiation has finished and was successful. ++ */ ++ private boolean saslNegotiated; ++ /** ++ * Boolean indication if SASL authentication has failed. When failed the server may end ++ * the connection. ++ */ ++ private boolean saslFailed; ++ private boolean resourceBinded; ++ private boolean sessionSupported; ++ /** ++ * The SASL related error condition if there was one provided by the server. ++ */ ++ private String errorCondition; ++ ++ static { ++ ++ // Register SASL mechanisms supported by Smack ++ registerSASLMechanism("EXTERNAL", SASLExternalMechanism.class); ++ registerSASLMechanism("GSSAPI", SASLGSSAPIMechanism.class); ++ registerSASLMechanism("DIGEST-MD5", SASLDigestMD5Mechanism.class); ++ registerSASLMechanism("CRAM-MD5", SASLCramMD5Mechanism.class); ++ registerSASLMechanism("PLAIN", SASLPlainMechanism.class); ++ registerSASLMechanism("ANONYMOUS", SASLAnonymous.class); ++ ++ supportSASLMechanism("GSSAPI",0); ++ supportSASLMechanism("DIGEST-MD5",1); ++ supportSASLMechanism("CRAM-MD5",2); ++ supportSASLMechanism("PLAIN",3); ++ supportSASLMechanism("ANONYMOUS",4); ++ ++ } ++ ++ /** ++ * Registers a new SASL mechanism ++ * ++ * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. ++ * @param mClass a SASLMechanism subclass. ++ */ ++ public static void registerSASLMechanism(String name, Class mClass) { ++ implementedMechanisms.put(name, mClass); ++ } ++ ++ /** ++ * Unregisters an existing SASL mechanism. Once the mechanism has been unregistered it won't ++ * be possible to authenticate users using the removed SASL mechanism. It also removes the ++ * mechanism from the supported list. ++ * ++ * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. ++ */ ++ public static void unregisterSASLMechanism(String name) { ++ implementedMechanisms.remove(name); ++ mechanismsPreferences.remove(name); ++ } ++ ++ ++ /** ++ * Registers a new SASL mechanism in the specified preference position. The client will try ++ * to authenticate using the most prefered SASL mechanism that is also supported by the server. ++ * The SASL mechanism must be registered via {@link #registerSASLMechanism(String, Class)} ++ * ++ * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. ++ */ ++ public static void supportSASLMechanism(String name) { ++ mechanismsPreferences.add(0, name); ++ } ++ ++ /** ++ * Registers a new SASL mechanism in the specified preference position. The client will try ++ * to authenticate using the most prefered SASL mechanism that is also supported by the server. ++ * Use the index parameter to set the level of preference of the new SASL mechanism. ++ * A value of 0 means that the mechanism is the most prefered one. The SASL mechanism must be ++ * registered via {@link #registerSASLMechanism(String, Class)} ++ * ++ * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. ++ * @param index preference position amongst all the implemented SASL mechanism. Starts with 0. ++ */ ++ public static void supportSASLMechanism(String name, int index) { ++ mechanismsPreferences.add(index, name); ++ } ++ ++ /** ++ * Un-supports an existing SASL mechanism. Once the mechanism has been unregistered it won't ++ * be possible to authenticate users using the removed SASL mechanism. Note that the mechanism ++ * is still registered, but will just not be used. ++ * ++ * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. ++ */ ++ public static void unsupportSASLMechanism(String name) { ++ mechanismsPreferences.remove(name); ++ } ++ ++ /** ++ * Returns the registerd SASLMechanism classes sorted by the level of preference. ++ * ++ * @return the registerd SASLMechanism classes sorted by the level of preference. ++ */ ++ public static List getRegisterSASLMechanisms() { ++ List answer = new ArrayList(); ++ for (String mechanismsPreference : mechanismsPreferences) { ++ answer.add(implementedMechanisms.get(mechanismsPreference)); ++ } ++ return answer; ++ } ++ ++ SASLAuthentication(Connection connection) { ++ super(); ++ this.connection = connection; ++ this.init(); ++ } ++ ++ /** ++ * Returns true if the server offered ANONYMOUS SASL as a way to authenticate users. ++ * ++ * @return true if the server offered ANONYMOUS SASL as a way to authenticate users. ++ */ ++ public boolean hasAnonymousAuthentication() { ++ return serverMechanisms.contains("ANONYMOUS"); ++ } ++ ++ /** ++ * Returns true if the server offered SASL authentication besides ANONYMOUS SASL. ++ * ++ * @return true if the server offered SASL authentication besides ANONYMOUS SASL. ++ */ ++ public boolean hasNonAnonymousAuthentication() { ++ return !serverMechanisms.isEmpty() && (serverMechanisms.size() != 1 || !hasAnonymousAuthentication()); ++ } ++ ++ /** ++ * Performs SASL authentication of the specified user. If SASL authentication was successful ++ * then resource binding and session establishment will be performed. This method will return ++ * the full JID provided by the server while binding a resource to the connection.

      ++ * ++ * The server may assign a full JID with a username or resource different than the requested ++ * by this method. ++ * ++ * @param username the username that is authenticating with the server. ++ * @param resource the desired resource. ++ * @param cbh the CallbackHandler used to get information from the user ++ * @return the full JID provided by the server while binding a resource to the connection. ++ * @throws XMPPException if an error occures while authenticating. ++ */ ++ public String authenticate(String username, String resource, CallbackHandler cbh) ++ throws XMPPException { ++ // Locate the SASLMechanism to use ++ String selectedMechanism = null; ++ for (String mechanism : mechanismsPreferences) { ++ if (implementedMechanisms.containsKey(mechanism) && ++ serverMechanisms.contains(mechanism)) { ++ selectedMechanism = mechanism; ++ break; ++ } ++ } ++ if (selectedMechanism != null) { ++ // A SASL mechanism was found. Authenticate using the selected mechanism and then ++ // proceed to bind a resource ++ try { ++ Class mechanismClass = implementedMechanisms.get(selectedMechanism); ++ Constructor constructor = mechanismClass.getConstructor(SASLAuthentication.class); ++ currentMechanism = (SASLMechanism) constructor.newInstance(this); ++ // Trigger SASL authentication with the selected mechanism. We use ++ // connection.getHost() since GSAPI requires the FQDN of the server, which ++ // may not match the XMPP domain. ++ currentMechanism.authenticate(username, connection.getHost(), cbh); ++ ++ // Wait until SASL negotiation finishes ++ synchronized (this) { ++ if (!saslNegotiated && !saslFailed) { ++ try { ++ wait(30000); ++ } ++ catch (InterruptedException e) { ++ // Ignore ++ } ++ } ++ } ++ ++ if (saslFailed) { ++ // SASL authentication failed and the server may have closed the connection ++ // so throw an exception ++ if (errorCondition != null) { ++ throw new XMPPException("SASL authentication " + ++ selectedMechanism + " failed: " + errorCondition); ++ } ++ else { ++ throw new XMPPException("SASL authentication failed using mechanism " + ++ selectedMechanism); ++ } ++ } ++ ++ if (saslNegotiated) { ++ // Bind a resource for this connection and ++ return bindResourceAndEstablishSession(resource); ++ } else { ++ // SASL authentication failed ++ } ++ } ++ catch (XMPPException e) { ++ throw e; ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } ++ else { ++ throw new XMPPException("SASL Authentication failed. No known authentication mechanisims."); ++ } ++ throw new XMPPException("SASL authentication failed"); ++ } ++ ++ /** ++ * Performs SASL authentication of the specified user. If SASL authentication was successful ++ * then resource binding and session establishment will be performed. This method will return ++ * the full JID provided by the server while binding a resource to the connection.

      ++ * ++ * The server may assign a full JID with a username or resource different than the requested ++ * by this method. ++ * ++ * @param username the username that is authenticating with the server. ++ * @param password the password to send to the server. ++ * @param resource the desired resource. ++ * @return the full JID provided by the server while binding a resource to the connection. ++ * @throws XMPPException if an error occures while authenticating. ++ */ ++ public String authenticate(String username, String password, String resource) ++ throws XMPPException { ++ // Locate the SASLMechanism to use ++ String selectedMechanism = null; ++ for (String mechanism : mechanismsPreferences) { ++ if (implementedMechanisms.containsKey(mechanism) && ++ serverMechanisms.contains(mechanism)) { ++ selectedMechanism = mechanism; ++ break; ++ } ++ } ++ if (selectedMechanism != null) { ++ // A SASL mechanism was found. Authenticate using the selected mechanism and then ++ // proceed to bind a resource ++ try { ++ Class mechanismClass = implementedMechanisms.get(selectedMechanism); ++ Constructor constructor = mechanismClass.getConstructor(SASLAuthentication.class); ++ currentMechanism = (SASLMechanism) constructor.newInstance(this); ++ // Trigger SASL authentication with the selected mechanism. We use ++ // connection.getHost() since GSAPI requires the FQDN of the server, which ++ // may not match the XMPP domain. ++ currentMechanism.authenticate(username, connection.getServiceName(), password); ++ ++ // Wait until SASL negotiation finishes ++ synchronized (this) { ++ if (!saslNegotiated && !saslFailed) { ++ try { ++ wait(30000); ++ } ++ catch (InterruptedException e) { ++ // Ignore ++ } ++ } ++ } ++ ++ if (saslFailed) { ++ // SASL authentication failed and the server may have closed the connection ++ // so throw an exception ++ if (errorCondition != null) { ++ throw new XMPPException("SASL authentication " + ++ selectedMechanism + " failed: " + errorCondition); ++ } ++ else { ++ throw new XMPPException("SASL authentication failed using mechanism " + ++ selectedMechanism); ++ } ++ } ++ ++ if (saslNegotiated) { ++ // Bind a resource for this connection and ++ return bindResourceAndEstablishSession(resource); ++ } ++ else { ++ // SASL authentication failed so try a Non-SASL authentication ++ return new NonSASLAuthentication(connection) ++ .authenticate(username, password, resource); ++ } ++ } ++ catch (XMPPException e) { ++ throw e; ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ // SASL authentication failed so try a Non-SASL authentication ++ return new NonSASLAuthentication(connection) ++ .authenticate(username, password, resource); ++ } ++ } ++ else { ++ // No SASL method was found so try a Non-SASL authentication ++ return new NonSASLAuthentication(connection).authenticate(username, password, resource); ++ } ++ } ++ ++ /** ++ * Performs ANONYMOUS SASL authentication. If SASL authentication was successful ++ * then resource binding and session establishment will be performed. This method will return ++ * the full JID provided by the server while binding a resource to the connection.

      ++ * ++ * The server will assign a full JID with a randomly generated resource and possibly with ++ * no username. ++ * ++ * @return the full JID provided by the server while binding a resource to the connection. ++ * @throws XMPPException if an error occures while authenticating. ++ */ ++ public String authenticateAnonymously() throws XMPPException { ++ try { ++ currentMechanism = new SASLAnonymous(this); ++ currentMechanism.authenticate(null,null,""); ++ ++ // Wait until SASL negotiation finishes ++ synchronized (this) { ++ if (!saslNegotiated && !saslFailed) { ++ try { ++ wait(5000); ++ } ++ catch (InterruptedException e) { ++ // Ignore ++ } ++ } ++ } ++ ++ if (saslFailed) { ++ // SASL authentication failed and the server may have closed the connection ++ // so throw an exception ++ if (errorCondition != null) { ++ throw new XMPPException("SASL authentication failed: " + errorCondition); ++ } ++ else { ++ throw new XMPPException("SASL authentication failed"); ++ } ++ } ++ ++ if (saslNegotiated) { ++ // Bind a resource for this connection and ++ return bindResourceAndEstablishSession(null); ++ } ++ else { ++ return new NonSASLAuthentication(connection).authenticateAnonymously(); ++ } ++ } catch (IOException e) { ++ return new NonSASLAuthentication(connection).authenticateAnonymously(); ++ } ++ } ++ ++ private String bindResourceAndEstablishSession(String resource) throws XMPPException { ++ // Wait until server sends response containing the element ++ synchronized (this) { ++ if (!resourceBinded) { ++ try { ++ wait(30000); ++ } ++ catch (InterruptedException e) { ++ // Ignore ++ } ++ } ++ } ++ ++ if (!resourceBinded) { ++ // Server never offered resource binding ++ throw new XMPPException("Resource binding not offered by server"); ++ } ++ ++ Bind bindResource = new Bind(); ++ bindResource.setResource(resource); ++ ++ PacketCollector collector = connection ++ .createPacketCollector(new PacketIDFilter(bindResource.getPacketID())); ++ // Send the packet ++ connection.sendPacket(bindResource); ++ // Wait up to a certain number of seconds for a response from the server. ++ Bind response = (Bind) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ // If the server replied with an error, throw an exception. ++ else if (response.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(response.getError()); ++ } ++ String userJID = response.getJid(); ++ ++ if (sessionSupported) { ++ Session session = new Session(); ++ collector = connection.createPacketCollector(new PacketIDFilter(session.getPacketID())); ++ // Send the packet ++ connection.sendPacket(session); ++ // Wait up to a certain number of seconds for a response from the server. ++ IQ ack = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ collector.cancel(); ++ if (ack == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ // If the server replied with an error, throw an exception. ++ else if (ack.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(ack.getError()); ++ } ++ } ++ else { ++ // Server never offered session establishment ++ throw new XMPPException("Session establishment not offered by server"); ++ } ++ return userJID; ++ } ++ ++ /** ++ * Sets the available SASL mechanism reported by the server. The server will report the ++ * available SASL mechanism once the TLS negotiation was successful. This information is ++ * stored and will be used when doing the authentication for logging in the user. ++ * ++ * @param mechanisms collection of strings with the available SASL mechanism reported ++ * by the server. ++ */ ++ void setAvailableSASLMethods(Collection mechanisms) { ++ this.serverMechanisms = mechanisms; ++ } ++ ++ /** ++ * Returns true if the user was able to authenticate with the server usins SASL. ++ * ++ * @return true if the user was able to authenticate with the server usins SASL. ++ */ ++ public boolean isAuthenticated() { ++ return saslNegotiated; ++ } ++ ++ /** ++ * The server is challenging the SASL authentication we just sent. Forward the challenge ++ * to the current SASLMechanism we are using. The SASLMechanism will send a response to ++ * the server. The length of the challenge-response sequence varies according to the ++ * SASLMechanism in use. ++ * ++ * @param challenge a base64 encoded string representing the challenge. ++ * @throws IOException If a network error occures while authenticating. ++ */ ++ void challengeReceived(String challenge) throws IOException { ++ currentMechanism.challengeReceived(challenge); ++ } ++ ++ /** ++ * Notification message saying that SASL authentication was successful. The next step ++ * would be to bind the resource. ++ */ ++ void authenticated() { ++ synchronized (this) { ++ saslNegotiated = true; ++ // Wake up the thread that is waiting in the #authenticate method ++ notify(); ++ } ++ } ++ ++ /** ++ * Notification message saying that SASL authentication has failed. The server may have ++ * closed the connection depending on the number of possible retries. ++ * ++ * @deprecated replaced by {@see #authenticationFailed(String)}. ++ */ ++ void authenticationFailed() { ++ authenticationFailed(null); ++ } ++ ++ /** ++ * Notification message saying that SASL authentication has failed. The server may have ++ * closed the connection depending on the number of possible retries. ++ * ++ * @param condition the error condition provided by the server. ++ */ ++ void authenticationFailed(String condition) { ++ synchronized (this) { ++ saslFailed = true; ++ errorCondition = condition; ++ // Wake up the thread that is waiting in the #authenticate method ++ notify(); ++ } ++ } ++ ++ /** ++ * Notification message saying that the server requires the client to bind a ++ * resource to the stream. ++ */ ++ void bindingRequired() { ++ synchronized (this) { ++ resourceBinded = true; ++ // Wake up the thread that is waiting in the #authenticate method ++ notify(); ++ } ++ } ++ ++ public void send(Packet stanza) { ++ connection.sendPacket(stanza); ++ } ++ ++ /** ++ * Notification message saying that the server supports sessions. When a server supports ++ * sessions the client needs to send a Session packet after successfully binding a resource ++ * for the session. ++ */ ++ void sessionsSupported() { ++ sessionSupported = true; ++ } ++ ++ /** ++ * Initializes the internal state in order to be able to be reused. The authentication ++ * is used by the connection at the first login and then reused after the connection ++ * is disconnected and then reconnected. ++ */ ++ protected void init() { ++ saslNegotiated = false; ++ saslFailed = false; ++ resourceBinded = false; ++ sessionSupported = false; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/ServerTrustManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/ServerTrustManager.java +new file mode 100644 +index 0000000..cabcd2c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/ServerTrustManager.java +@@ -0,0 +1,336 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2005 Jive Software. ++ * ++ * All rights reserved. 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 javax.net.ssl.X509TrustManager; ++import java.io.FileInputStream; ++import java.io.InputStream; ++import java.io.IOException; ++import java.security.*; ++import java.security.cert.CertificateException; ++import java.security.cert.CertificateParsingException; ++import java.security.cert.X509Certificate; ++import java.util.*; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++ ++/** ++ * Trust manager that checks all certificates presented by the server. This class ++ * is used during TLS negotiation. It is possible to disable/enable some or all checkings ++ * by configuring the {@link ConnectionConfiguration}. The truststore file that contains ++ * knows and trusted CA root certificates can also be configure in {@link ConnectionConfiguration}. ++ * ++ * @author Gaston Dombiak ++ */ ++class ServerTrustManager implements X509TrustManager { ++ ++ private static Pattern cnPattern = Pattern.compile("(?i)(cn=)([^,]*)"); ++ ++ private ConnectionConfiguration configuration; ++ ++ /** ++ * Holds the domain of the remote server we are trying to connect ++ */ ++ private String server; ++ private KeyStore trustStore; ++ ++ private static Map stores = new HashMap(); ++ ++ public ServerTrustManager(String server, ConnectionConfiguration configuration) { ++ this.configuration = configuration; ++ this.server = server; ++ ++ InputStream in = null; ++ synchronized (stores) { ++ KeyStoreOptions options = new KeyStoreOptions(configuration.getTruststoreType(), ++ configuration.getTruststorePath(), configuration.getTruststorePassword()); ++ if (stores.containsKey(options)) { ++ trustStore = stores.get(options); ++ } else { ++ try { ++ trustStore = KeyStore.getInstance(options.getType()); ++ in = new FileInputStream(options.getPath()); ++ trustStore.load(in, options.getPassword().toCharArray()); ++ } ++ catch (Exception e) { ++ trustStore = null; ++ e.printStackTrace(); ++ } ++ finally { ++ if (in != null) { ++ try { ++ in.close(); ++ } ++ catch (IOException ioe) { ++ // Ignore. ++ } ++ } ++ } ++ stores.put(options, trustStore); ++ } ++ if (trustStore == null) ++ // Disable root CA checking ++ configuration.setVerifyRootCAEnabled(false); ++ } ++ } ++ ++ public X509Certificate[] getAcceptedIssuers() { ++ return new X509Certificate[0]; ++ } ++ ++ public void checkClientTrusted(X509Certificate[] arg0, String arg1) ++ throws CertificateException { ++ } ++ ++ public void checkServerTrusted(X509Certificate[] x509Certificates, String arg1) ++ throws CertificateException { ++ ++ int nSize = x509Certificates.length; ++ ++ List peerIdentities = getPeerIdentity(x509Certificates[0]); ++ ++ if (configuration.isVerifyChainEnabled()) { ++ // Working down the chain, for every certificate in the chain, ++ // verify that the subject of the certificate is the issuer of the ++ // next certificate in the chain. ++ Principal principalLast = null; ++ for (int i = nSize -1; i >= 0 ; i--) { ++ X509Certificate x509certificate = x509Certificates[i]; ++ Principal principalIssuer = x509certificate.getIssuerDN(); ++ Principal principalSubject = x509certificate.getSubjectDN(); ++ if (principalLast != null) { ++ if (principalIssuer.equals(principalLast)) { ++ try { ++ PublicKey publickey = ++ x509Certificates[i + 1].getPublicKey(); ++ x509Certificates[i].verify(publickey); ++ } ++ catch (GeneralSecurityException generalsecurityexception) { ++ throw new CertificateException( ++ "signature verification failed of " + peerIdentities); ++ } ++ } ++ else { ++ throw new CertificateException( ++ "subject/issuer verification failed of " + peerIdentities); ++ } ++ } ++ principalLast = principalSubject; ++ } ++ } ++ ++ if (configuration.isVerifyRootCAEnabled()) { ++ // Verify that the the last certificate in the chain was issued ++ // by a third-party that the client trusts. ++ boolean trusted = false; ++ try { ++ trusted = trustStore.getCertificateAlias(x509Certificates[nSize - 1]) != null; ++ if (!trusted && nSize == 1 && configuration.isSelfSignedCertificateEnabled()) ++ { ++ System.out.println("Accepting self-signed certificate of remote server: " + ++ peerIdentities); ++ trusted = true; ++ } ++ } ++ catch (KeyStoreException e) { ++ e.printStackTrace(); ++ } ++ if (!trusted) { ++ throw new CertificateException("root certificate not trusted of " + peerIdentities); ++ } ++ } ++ ++ if (configuration.isNotMatchingDomainCheckEnabled()) { ++ // Verify that the first certificate in the chain corresponds to ++ // the server we desire to authenticate. ++ // Check if the certificate uses a wildcard indicating that subdomains are valid ++ if (peerIdentities.size() == 1 && peerIdentities.get(0).startsWith("*.")) { ++ // Remove the wildcard ++ String peerIdentity = peerIdentities.get(0).replace("*.", ""); ++ // Check if the requested subdomain matches the certified domain ++ if (!server.endsWith(peerIdentity)) { ++ throw new CertificateException("target verification failed of " + peerIdentities); ++ } ++ } ++ else if (!peerIdentities.contains(server)) { ++ throw new CertificateException("target verification failed of " + peerIdentities); ++ } ++ } ++ ++ if (configuration.isExpiredCertificatesCheckEnabled()) { ++ // For every certificate in the chain, verify that the certificate ++ // is valid at the current time. ++ Date date = new Date(); ++ for (int i = 0; i < nSize; i++) { ++ try { ++ x509Certificates[i].checkValidity(date); ++ } ++ catch (GeneralSecurityException generalsecurityexception) { ++ throw new CertificateException("invalid date of " + server); ++ } ++ } ++ } ++ ++ } ++ ++ /** ++ * Returns the identity of the remote server as defined in the specified certificate. The ++ * identity is defined in the subjectDN of the certificate and it can also be defined in ++ * the subjectAltName extension of type "xmpp". When the extension is being used then the ++ * identity defined in the extension in going to be returned. Otherwise, the value stored in ++ * the subjectDN is returned. ++ * ++ * @param x509Certificate the certificate the holds the identity of the remote server. ++ * @return the identity of the remote server as defined in the specified certificate. ++ */ ++ public static List getPeerIdentity(X509Certificate x509Certificate) { ++ // Look the identity in the subjectAltName extension if available ++ List names = getSubjectAlternativeNames(x509Certificate); ++ if (names.isEmpty()) { ++ String name = x509Certificate.getSubjectDN().getName(); ++ Matcher matcher = cnPattern.matcher(name); ++ if (matcher.find()) { ++ name = matcher.group(2); ++ } ++ // Create an array with the unique identity ++ names = new ArrayList(); ++ names.add(name); ++ } ++ return names; ++ } ++ ++ /** ++ * Returns the JID representation of an XMPP entity contained as a SubjectAltName extension ++ * in the certificate. If none was found then return null. ++ * ++ * @param certificate the certificate presented by the remote entity. ++ * @return the JID representation of an XMPP entity contained as a SubjectAltName extension ++ * in the certificate. If none was found then return null. ++ */ ++ private static List getSubjectAlternativeNames(X509Certificate certificate) { ++ List identities = new ArrayList(); ++ try { ++ Collection> altNames = certificate.getSubjectAlternativeNames(); ++ // Check that the certificate includes the SubjectAltName extension ++ if (altNames == null) { ++ return Collections.emptyList(); ++ } ++ // Use the type OtherName to search for the certified server name ++ /*for (List item : altNames) { ++ Integer type = (Integer) item.get(0); ++ if (type == 0) { ++ // Type OtherName found so return the associated value ++ try { ++ // Value is encoded using ASN.1 so decode it to get the server's identity ++ ASN1InputStream decoder = new ASN1InputStream((byte[]) item.toArray()[1]); ++ DEREncodable encoded = decoder.readObject(); ++ encoded = ((DERSequence) encoded).getObjectAt(1); ++ encoded = ((DERTaggedObject) encoded).getObject(); ++ encoded = ((DERTaggedObject) encoded).getObject(); ++ String identity = ((DERUTF8String) encoded).getString(); ++ // Add the decoded server name to the list of identities ++ identities.add(identity); ++ } ++ catch (UnsupportedEncodingException e) { ++ // Ignore ++ } ++ catch (IOException e) { ++ // Ignore ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } ++ // Other types are not good for XMPP so ignore them ++ System.out.println("SubjectAltName of invalid type found: " + certificate); ++ }*/ ++ } ++ catch (CertificateParsingException e) { ++ e.printStackTrace(); ++ } ++ return identities; ++ } ++ ++ private static class KeyStoreOptions { ++ private final String type; ++ private final String path; ++ private final String password; ++ ++ public KeyStoreOptions(String type, String path, String password) { ++ super(); ++ this.type = type; ++ this.path = path; ++ this.password = password; ++ } ++ ++ public String getType() { ++ return type; ++ } ++ ++ public String getPath() { ++ return path; ++ } ++ ++ public String getPassword() { ++ return password; ++ } ++ ++ @Override ++ public int hashCode() { ++ final int prime = 31; ++ int result = 1; ++ result = prime * result ++ + ((password == null) ? 0 : password.hashCode()); ++ result = prime * result + ((path == null) ? 0 : path.hashCode()); ++ result = prime * result + ((type == null) ? 0 : type.hashCode()); ++ return result; ++ } ++ ++ @Override ++ public boolean equals(Object obj) { ++ if (this == obj) ++ return true; ++ if (obj == null) ++ return false; ++ if (getClass() != obj.getClass()) ++ return false; ++ KeyStoreOptions other = (KeyStoreOptions) obj; ++ if (password == null) { ++ if (other.password != null) ++ return false; ++ } else if (!password.equals(other.password)) ++ return false; ++ if (path == null) { ++ if (other.path != null) ++ return false; ++ } else if (!path.equals(other.path)) ++ return false; ++ if (type == null) { ++ if (other.type != null) ++ return false; ++ } else if (!type.equals(other.type)) ++ return false; ++ return true; ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/SmackConfiguration.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/SmackConfiguration.java +new file mode 100644 +index 0000000..4c622df +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/SmackConfiguration.java +@@ -0,0 +1,344 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.xmlpull.v1.XmlPullParserFactory; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.io.InputStream; ++import java.net.URL; ++import java.util.*; ++ ++/** ++ * Represents the configuration of Smack. The configuration is used for: ++ *

        ++ *
      • Initializing classes by loading them at start-up. ++ *
      • Getting the current Smack version. ++ *
      • Getting and setting global library behavior, such as the period of time ++ * to wait for replies to packets from the server. Note: setting these values ++ * via the API will override settings in the configuration file. ++ *
      ++ * ++ * Configuration settings are stored in META-INF/smack-config.xml (typically inside the ++ * smack.jar file). ++ * ++ * @author Gaston Dombiak ++ */ ++public final class SmackConfiguration { ++ ++ private static final String SMACK_VERSION = "3.2.2"; ++ ++ private static int packetReplyTimeout = 5000; ++ private static int keepAliveInterval = 30000; ++ private static Vector defaultMechs = new Vector(); ++ ++ private static boolean localSocks5ProxyEnabled = true; ++ private static int localSocks5ProxyPort = 7777; ++ private static int packetCollectorSize = 5000; ++ ++ private SmackConfiguration() { ++ } ++ ++ /** ++ * Loads the configuration from the smack-config.xml file.

      ++ * ++ * So far this means that: ++ * 1) a set of classes will be loaded in order to execute their static init block ++ * 2) retrieve and set the current Smack release ++ */ ++ static { ++ try { ++ // Get an array of class loaders to try loading the providers files from. ++ ClassLoader[] classLoaders = getClassLoaders(); ++ for (ClassLoader classLoader : classLoaders) { ++ Enumeration configEnum = classLoader.getResources("META-INF/smack-config.xml"); ++ while (configEnum.hasMoreElements()) { ++ URL url = (URL) configEnum.nextElement(); ++ InputStream systemStream = null; ++ try { ++ systemStream = url.openStream(); ++ XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); ++ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); ++ parser.setInput(systemStream, "UTF-8"); ++ int eventType = parser.getEventType(); ++ do { ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("className")) { ++ // Attempt to load the class so that the class can get initialized ++ parseClassToLoad(parser); ++ } ++ else if (parser.getName().equals("packetReplyTimeout")) { ++ packetReplyTimeout = parseIntProperty(parser, packetReplyTimeout); ++ } ++ else if (parser.getName().equals("keepAliveInterval")) { ++ keepAliveInterval = parseIntProperty(parser, keepAliveInterval); ++ } ++ else if (parser.getName().equals("mechName")) { ++ defaultMechs.add(parser.nextText()); ++ } ++ else if (parser.getName().equals("localSocks5ProxyEnabled")) { ++ localSocks5ProxyEnabled = Boolean.parseBoolean(parser.nextText()); ++ } ++ else if (parser.getName().equals("localSocks5ProxyPort")) { ++ localSocks5ProxyPort = parseIntProperty(parser, localSocks5ProxyPort); ++ } ++ else if (parser.getName().equals("packetCollectorSize")) { ++ packetCollectorSize = parseIntProperty(parser, packetCollectorSize); ++ } ++ } ++ eventType = parser.next(); ++ } ++ while (eventType != XmlPullParser.END_DOCUMENT); ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ } ++ finally { ++ try { ++ systemStream.close(); ++ } ++ catch (Exception e) { ++ // Ignore. ++ } ++ } ++ } ++ } ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } ++ ++ /** ++ * Returns the Smack version information, eg "1.3.0". ++ * ++ * @return the Smack version information. ++ */ ++ public static String getVersion() { ++ return SMACK_VERSION; ++ } ++ ++ /** ++ * Returns the number of milliseconds to wait for a response from ++ * the server. The default value is 5000 ms. ++ * ++ * @return the milliseconds to wait for a response from the server ++ */ ++ public static int getPacketReplyTimeout() { ++ // The timeout value must be greater than 0 otherwise we will answer the default value ++ if (packetReplyTimeout <= 0) { ++ packetReplyTimeout = 5000; ++ } ++ return packetReplyTimeout; ++ } ++ ++ /** ++ * Sets the number of milliseconds to wait for a response from ++ * the server. ++ * ++ * @param timeout the milliseconds to wait for a response from the server ++ */ ++ public static void setPacketReplyTimeout(int timeout) { ++ if (timeout <= 0) { ++ throw new IllegalArgumentException(); ++ } ++ packetReplyTimeout = timeout; ++ } ++ ++ /** ++ * Returns the number of milleseconds delay between sending keep-alive ++ * requests to the server. The default value is 30000 ms. A value of -1 ++ * mean no keep-alive requests will be sent to the server. ++ * ++ * @return the milliseconds to wait between keep-alive requests, or -1 if ++ * no keep-alive should be sent. ++ */ ++ public static int getKeepAliveInterval() { ++ return keepAliveInterval; ++ } ++ ++ /** ++ * Sets the number of milleseconds delay between sending keep-alive ++ * requests to the server. The default value is 30000 ms. A value of -1 ++ * mean no keep-alive requests will be sent to the server. ++ * ++ * @param interval the milliseconds to wait between keep-alive requests, ++ * or -1 if no keep-alive should be sent. ++ */ ++ public static void setKeepAliveInterval(int interval) { ++ keepAliveInterval = interval; ++ } ++ ++ /** ++ * Gets the default max size of a packet collector before it will delete ++ * the older packets. ++ * ++ * @return The number of packets to queue before deleting older packets. ++ */ ++ public static int getPacketCollectorSize() { ++ return packetCollectorSize; ++ } ++ ++ /** ++ * Sets the default max size of a packet collector before it will delete ++ * the older packets. ++ * ++ * @param The number of packets to queue before deleting older packets. ++ */ ++ public static void setPacketCollectorSize(int collectorSize) { ++ packetCollectorSize = collectorSize; ++ } ++ ++ /** ++ * Add a SASL mechanism to the list to be used. ++ * ++ * @param mech the SASL mechanism to be added ++ */ ++ public static void addSaslMech(String mech) { ++ if(! defaultMechs.contains(mech) ) { ++ defaultMechs.add(mech); ++ } ++ } ++ ++ /** ++ * Add a Collection of SASL mechanisms to the list to be used. ++ * ++ * @param mechs the Collection of SASL mechanisms to be added ++ */ ++ public static void addSaslMechs(Collection mechs) { ++ for(String mech : mechs) { ++ addSaslMech(mech); ++ } ++ } ++ ++ /** ++ * Remove a SASL mechanism from the list to be used. ++ * ++ * @param mech the SASL mechanism to be removed ++ */ ++ public static void removeSaslMech(String mech) { ++ if( defaultMechs.contains(mech) ) { ++ defaultMechs.remove(mech); ++ } ++ } ++ ++ /** ++ * Remove a Collection of SASL mechanisms to the list to be used. ++ * ++ * @param mechs the Collection of SASL mechanisms to be removed ++ */ ++ public static void removeSaslMechs(Collection mechs) { ++ for(String mech : mechs) { ++ removeSaslMech(mech); ++ } ++ } ++ ++ /** ++ * Returns the list of SASL mechanisms to be used. If a SASL mechanism is ++ * listed here it does not guarantee it will be used. The server may not ++ * support it, or it may not be implemented. ++ * ++ * @return the list of SASL mechanisms to be used. ++ */ ++ public static List getSaslMechs() { ++ return defaultMechs; ++ } ++ ++ /** ++ * Returns true if the local Socks5 proxy should be started. Default is true. ++ * ++ * @return if the local Socks5 proxy should be started ++ */ ++ public static boolean isLocalSocks5ProxyEnabled() { ++ return localSocks5ProxyEnabled; ++ } ++ ++ /** ++ * Sets if the local Socks5 proxy should be started. Default is true. ++ * ++ * @param localSocks5ProxyEnabled if the local Socks5 proxy should be started ++ */ ++ public static void setLocalSocks5ProxyEnabled(boolean localSocks5ProxyEnabled) { ++ SmackConfiguration.localSocks5ProxyEnabled = localSocks5ProxyEnabled; ++ } ++ ++ /** ++ * Return the port of the local Socks5 proxy. Default is 7777. ++ * ++ * @return the port of the local Socks5 proxy ++ */ ++ public static int getLocalSocks5ProxyPort() { ++ return localSocks5ProxyPort; ++ } ++ ++ /** ++ * Sets the port of the local Socks5 proxy. Default is 7777. If you set the port to a negative ++ * value Smack tries the absolute value and all following until it finds an open port. ++ * ++ * @param localSocks5ProxyPort the port of the local Socks5 proxy to set ++ */ ++ public static void setLocalSocks5ProxyPort(int localSocks5ProxyPort) { ++ SmackConfiguration.localSocks5ProxyPort = localSocks5ProxyPort; ++ } ++ ++ private static void parseClassToLoad(XmlPullParser parser) throws Exception { ++ String className = parser.nextText(); ++ // Attempt to load the class so that the class can get initialized ++ try { ++ Class.forName(className); ++ } ++ catch (ClassNotFoundException cnfe) { ++ System.err.println("Error! A startup class specified in smack-config.xml could " + ++ "not be loaded: " + className); ++ } ++ } ++ ++ private static int parseIntProperty(XmlPullParser parser, int defaultValue) ++ throws Exception ++ { ++ try { ++ return Integer.parseInt(parser.nextText()); ++ } ++ catch (NumberFormatException nfe) { ++ nfe.printStackTrace(); ++ return defaultValue; ++ } ++ } ++ ++ /** ++ * Returns an array of class loaders to load resources from. ++ * ++ * @return an array of ClassLoader instances. ++ */ ++ private static ClassLoader[] getClassLoaders() { ++ ClassLoader[] classLoaders = new ClassLoader[2]; ++ classLoaders[0] = SmackConfiguration.class.getClassLoader(); ++ classLoaders[1] = Thread.currentThread().getContextClassLoader(); ++ // Clean up possible null values. Note that #getClassLoader may return a null value. ++ List loaders = new ArrayList(); ++ for (ClassLoader classLoader : classLoaders) { ++ if (classLoader != null) { ++ loaders.add(classLoader); ++ } ++ } ++ return loaders.toArray(new ClassLoader[loaders.size()]); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/UserAuthentication.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/UserAuthentication.java +new file mode 100644 +index 0000000..1c3c50f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/UserAuthentication.java +@@ -0,0 +1,79 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++ ++/** ++ * There are two ways to authenticate a user with a server. Using SASL or Non-SASL ++ * authentication. This interface makes {@link SASLAuthentication} and ++ * {@link NonSASLAuthentication} polyphormic. ++ * ++ * @author Gaston Dombiak ++ * @author Jay Kline ++ */ ++interface UserAuthentication { ++ ++ /** ++ * Authenticates the user with the server. This method will return the full JID provided by ++ * the server. The server may assign a full JID with a username and resource different than ++ * requested by this method. ++ * ++ * Note that using callbacks is the prefered method of authenticating users since it allows ++ * more flexability in the mechanisms used. ++ * ++ * @param username the requested username (authorization ID) for authenticating to the server ++ * @param resource the requested resource. ++ * @param cbh the CallbackHandler used to obtain authentication ID, password, or other ++ * information ++ * @return the full JID provided by the server while binding a resource for the connection. ++ * @throws XMPPException if an error occurs while authenticating. ++ */ ++ String authenticate(String username, String resource, CallbackHandler cbh) throws ++ XMPPException; ++ ++ /** ++ * Authenticates the user with the server. This method will return the full JID provided by ++ * the server. The server may assign a full JID with a username and resource different than ++ * the requested by this method. ++ * ++ * It is recommended that @{link #authenticate(String, String, CallbackHandler)} be used instead ++ * since it provides greater flexability in authenticaiton and authorization. ++ * ++ * @param username the username that is authenticating with the server. ++ * @param password the password to send to the server. ++ * @param resource the desired resource. ++ * @return the full JID provided by the server while binding a resource for the connection. ++ * @throws XMPPException if an error occures while authenticating. ++ */ ++ String authenticate(String username, String password, String resource) throws ++ XMPPException; ++ ++ /** ++ * Performs an anonymous authentication with the server. The server will created a new full JID ++ * for this connection. An exception will be thrown if the server does not support anonymous ++ * authentication. ++ * ++ * @return the full JID provided by the server while binding a resource for the connection. ++ * @throws XMPPException if an error occures while authenticating. ++ */ ++ String authenticateAnonymously() throws XMPPException; ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPConnection.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPConnection.java +new file mode 100644 +index 0000000..659414b +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPConnection.java +@@ -0,0 +1,1099 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.Presence; ++import org.jivesoftware.smack.packet.XMPPError; ++import org.jivesoftware.smack.util.StringUtils; ++ ++import javax.net.ssl.KeyManager; ++import javax.net.ssl.KeyManagerFactory; ++import javax.net.ssl.SSLContext; ++import javax.net.ssl.SSLSocket; ++import org.apache.harmony.javax.security.auth.callback.Callback; ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++import org.apache.harmony.javax.security.auth.callback.PasswordCallback; ++import java.io.*; ++import java.lang.reflect.Constructor; ++import java.lang.reflect.Method; ++import java.net.Socket; ++import java.net.UnknownHostException; ++import java.security.KeyStore; ++import java.security.Provider; ++import java.security.Security; ++import java.util.Collection; ++ ++/** ++ * Creates a socket connection to a XMPP server. This is the default connection ++ * to a Jabber server and is specified in the XMPP Core (RFC 3920). ++ * ++ * @see Connection ++ * @author Matt Tucker ++ */ ++public class XMPPConnection extends Connection { ++ ++ /** ++ * The socket which is used for this connection. ++ */ ++ protected Socket socket; ++ ++ private String user = null; ++ protected boolean connected = false; ++ private boolean socketClosed = false; ++ /** ++ * Flag that indicates if the user is currently authenticated with the server. ++ */ ++ private boolean authenticated = false; ++ /** ++ * Flag that indicates if the user was authenticated with the server when the connection ++ * to the server was closed (abruptly or not). ++ */ ++ private boolean wasAuthenticated = false; ++ private boolean anonymous = false; ++ private boolean usingTLS = false; ++ ++ PacketWriter packetWriter; ++ PacketReader packetReader; ++ ++ Roster roster = null; ++ ++ /** ++ * Collection of available stream compression methods offered by the server. ++ */ ++ private Collection compressionMethods; ++ /** ++ * Flag that indicates if stream compression is actually in use. ++ */ ++ private boolean usingCompression; ++ ++ ++ /** ++ * Creates a new connection to the specified XMPP server. A DNS SRV lookup will be ++ * performed to determine the IP address and port corresponding to the ++ * service name; if that lookup fails, it's assumed that server resides at ++ * serviceName with the default port of 5222. Encrypted connections (TLS) ++ * will be used if available, stream compression is disabled, and standard SASL ++ * mechanisms will be used for authentication.

      ++ *

      ++ * This is the simplest constructor for connecting to an XMPP server. Alternatively, ++ * you can get fine-grained control over connection settings using the ++ * {@link #XMPPConnection(ConnectionConfiguration)} constructor.

      ++ *

      ++ * Note that XMPPConnection constructors do not establish a connection to the server ++ * and you must call {@link #connect()}.

      ++ *

      ++ * The CallbackHandler will only be used if the connection requires the client provide ++ * an SSL certificate to the server. The CallbackHandler must handle the PasswordCallback ++ * to prompt for a password to unlock the keystore containing the SSL certificate. ++ * ++ * @param serviceName the name of the XMPP server to connect to; e.g. example.com. ++ * @param callbackHandler the CallbackHandler used to prompt for the password to the keystore. ++ */ ++ public XMPPConnection(String serviceName, CallbackHandler callbackHandler) { ++ // Create the configuration for this new connection ++ super(new ConnectionConfiguration(serviceName)); ++ config.setCompressionEnabled(false); ++ config.setSASLAuthenticationEnabled(true); ++ config.setDebuggerEnabled(DEBUG_ENABLED); ++ config.setCallbackHandler(callbackHandler); ++ } ++ ++ /** ++ * Creates a new XMPP connection in the same way {@link #XMPPConnection(String,CallbackHandler)} does, but ++ * with no callback handler for password prompting of the keystore. This will work ++ * in most cases, provided the client is not required to provide a certificate to ++ * the server. ++ * ++ * @param serviceName the name of the XMPP server to connect to; e.g. example.com. ++ */ ++ public XMPPConnection(String serviceName) { ++ // Create the configuration for this new connection ++ super(new ConnectionConfiguration(serviceName)); ++ config.setCompressionEnabled(false); ++ config.setSASLAuthenticationEnabled(true); ++ config.setDebuggerEnabled(DEBUG_ENABLED); ++ } ++ ++ /** ++ * Creates a new XMPP connection in the same way {@link #XMPPConnection(ConnectionConfiguration,CallbackHandler)} does, but ++ * with no callback handler for password prompting of the keystore. This will work ++ * in most cases, provided the client is not required to provide a certificate to ++ * the server. ++ * ++ * ++ * @param config the connection configuration. ++ */ ++ public XMPPConnection(ConnectionConfiguration config) { ++ super(config); ++ } ++ ++ /** ++ * Creates a new XMPP connection using the specified connection configuration.

      ++ *

      ++ * Manually specifying connection configuration information is suitable for ++ * advanced users of the API. In many cases, using the ++ * {@link #XMPPConnection(String)} constructor is a better approach.

      ++ *

      ++ * Note that XMPPConnection constructors do not establish a connection to the server ++ * and you must call {@link #connect()}.

      ++ *

      ++ * ++ * The CallbackHandler will only be used if the connection requires the client provide ++ * an SSL certificate to the server. The CallbackHandler must handle the PasswordCallback ++ * to prompt for a password to unlock the keystore containing the SSL certificate. ++ * ++ * @param config the connection configuration. ++ * @param callbackHandler the CallbackHandler used to prompt for the password to the keystore. ++ */ ++ public XMPPConnection(ConnectionConfiguration config, CallbackHandler callbackHandler) { ++ super(config); ++ config.setCallbackHandler(callbackHandler); ++ } ++ ++ public String getConnectionID() { ++ if (!isConnected()) { ++ return null; ++ } ++ return connectionID; ++ } ++ ++ public String getUser() { ++ if (!isAuthenticated()) { ++ return null; ++ } ++ return user; ++ } ++ ++ @Override ++ public synchronized void login(String username, String password, String resource) throws XMPPException { ++ if (!isConnected()) { ++ throw new IllegalStateException("Not connected to server."); ++ } ++ if (authenticated) { ++ throw new IllegalStateException("Already logged in to server."); ++ } ++ // Do partial version of nameprep on the username. ++ username = username.toLowerCase().trim(); ++ ++ String response; ++ if (config.isSASLAuthenticationEnabled() && ++ saslAuthentication.hasNonAnonymousAuthentication()) { ++ // Authenticate using SASL ++ if (password != null) { ++ response = saslAuthentication.authenticate(username, password, resource); ++ } ++ else { ++ response = saslAuthentication ++ .authenticate(username, resource, config.getCallbackHandler()); ++ } ++ } ++ else { ++ // Authenticate using Non-SASL ++ response = new NonSASLAuthentication(this).authenticate(username, password, resource); ++ } ++ ++ // If compression is enabled then request the server to use stream compression ++ if (config.isCompressionEnabled()) { ++ useCompression(); ++ } ++ ++ // Indicate that we're now authenticated. ++ authenticated = true; ++ anonymous = false; ++ ++ // If we did not bind, don't attempt to do anything with roster or presence ++ if (resource == null) ++ return; ++ ++ // Set the user. ++ if (response != null) { ++ this.user = response; ++ // Update the serviceName with the one returned by the server ++ config.setServiceName(StringUtils.parseServer(response)); ++ } ++ else { ++ this.user = username + "@" + getServiceName(); ++ if (resource != null) { ++ this.user += "/" + resource; ++ } ++ } ++ ++ // Create the roster if it is not a reconnection or roster already created by getRoster() ++ if (this.roster == null) { ++ if(rosterStorage==null){ ++ this.roster = new Roster(this); ++ } ++ else{ ++ this.roster = new Roster(this,rosterStorage); ++ } ++ } ++ if (config.isRosterLoadedAtLogin()) { ++ this.roster.reload(); ++ } ++ ++ // Set presence to online. ++ if (config.isSendPresence()) { ++ packetWriter.sendPacket(new Presence(Presence.Type.available)); ++ } ++ ++ // Stores the authentication for future reconnection ++ config.setLoginInfo(username, password, resource); ++ ++ // If debugging is enabled, change the the debug window title to include the ++ // name we are now logged-in as. ++ // If DEBUG_ENABLED was set to true AFTER the connection was created the debugger ++ // will be null ++ if (config.isDebuggerEnabled() && debugger != null) { ++ debugger.userHasLogged(user); ++ } ++ } ++ ++ @Override ++ public synchronized void loginAnonymously() throws XMPPException { ++ if (!isConnected()) { ++ throw new IllegalStateException("Not connected to server."); ++ } ++ if (authenticated) { ++ throw new IllegalStateException("Already logged in to server."); ++ } ++ ++ String response; ++ if (config.isSASLAuthenticationEnabled() && ++ saslAuthentication.hasAnonymousAuthentication()) { ++ response = saslAuthentication.authenticateAnonymously(); ++ } ++ else { ++ // Authenticate using Non-SASL ++ response = new NonSASLAuthentication(this).authenticateAnonymously(); ++ } ++ ++ // Set the user value. ++ this.user = response; ++ // Update the serviceName with the one returned by the server ++ config.setServiceName(StringUtils.parseServer(response)); ++ ++ // If compression is enabled then request the server to use stream compression ++ if (config.isCompressionEnabled()) { ++ useCompression(); ++ } ++ ++ // Set presence to online. ++ packetWriter.sendPacket(new Presence(Presence.Type.available)); ++ ++ // Indicate that we're now authenticated. ++ authenticated = true; ++ anonymous = true; ++ ++ // If debugging is enabled, change the the debug window title to include the ++ // name we are now logged-in as. ++ // If DEBUG_ENABLED was set to true AFTER the connection was created the debugger ++ // will be null ++ if (config.isDebuggerEnabled() && debugger != null) { ++ debugger.userHasLogged(user); ++ } ++ } ++ ++ public Roster getRoster() { ++ // synchronize against login() ++ synchronized(this) { ++ // if connection is authenticated the roster is already set by login() ++ // or a previous call to getRoster() ++ if (!isAuthenticated() || isAnonymous()) { ++ if (roster == null) { ++ roster = new Roster(this); ++ } ++ return roster; ++ } ++ } ++ ++ if (!config.isRosterLoadedAtLogin()) { ++ roster.reload(); ++ } ++ // If this is the first time the user has asked for the roster after calling ++ // login, we want to wait for the server to send back the user's roster. This ++ // behavior shields API users from having to worry about the fact that roster ++ // operations are asynchronous, although they'll still have to listen for ++ // changes to the roster. Note: because of this waiting logic, internal ++ // Smack code should be wary about calling the getRoster method, and may need to ++ // access the roster object directly. ++ if (!roster.rosterInitialized) { ++ try { ++ synchronized (roster) { ++ long waitTime = SmackConfiguration.getPacketReplyTimeout(); ++ long start = System.currentTimeMillis(); ++ while (!roster.rosterInitialized) { ++ if (waitTime <= 0) { ++ break; ++ } ++ roster.wait(waitTime); ++ long now = System.currentTimeMillis(); ++ waitTime -= now - start; ++ start = now; ++ } ++ } ++ } ++ catch (InterruptedException ie) { ++ // Ignore. ++ } ++ } ++ return roster; ++ } ++ ++ public boolean isConnected() { ++ return connected; ++ } ++ ++ public boolean isSecureConnection() { ++ return isUsingTLS(); ++ } ++ ++ public boolean isSocketClosed() { ++ return socketClosed; ++ } ++ ++ public boolean isAuthenticated() { ++ return authenticated; ++ } ++ ++ public boolean isAnonymous() { ++ return anonymous; ++ } ++ ++ /** ++ * Forceful shutdown without full disconnect. ++ * ++ *

      Used when a reconnection is possible. ++ */ ++ public void quickShutdown() { ++ try { ++ this.setWasAuthenticated(authenticated); ++ authenticated = false; ++ ++ // Close socket before we close writer, so that stream end is not sent and reconnection is possible. ++ // This may cause a ConnectionClosedOnError notification to go out. ++ socketClosed = true; ++ try { ++ socket.shutdownInput(); ++ } ++ catch (Exception e) { ++ } ++ try { ++ socket.close(); ++ } catch (Exception e) { ++ } ++ ++ if (packetReader!=null){ ++ // Does not notify listeners ++ packetReader.quickShutdown(); ++ } ++ if (packetWriter!=null){ ++ packetWriter.shutdown(); ++ } ++ // Wait 150 ms for processes to clean-up, then shutdown. ++ try { ++ Thread.sleep(150); ++ } ++ catch (Exception e) { ++ // Ignore. ++ } ++ ++ connected = false; ++ ++ // Close down the readers and writers. ++ if (reader != null) { ++ try { ++ reader.close(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ reader = null; ++ } ++ if (writer != null) { ++ try { ++ writer.close(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ writer = null; ++ } ++ ++ saslAuthentication.init(); ++ } ++ catch (Exception e) { ++ System.err.println(e); ++ } ++ } ++ ++ /** ++ * Closes the connection by setting presence to unavailable then closing the stream to ++ * the XMPP server. The shutdown logic will be used during a planned disconnection or when ++ * dealing with an unexpected disconnection. Unlike {@link #disconnect()} the connection's ++ * packet reader, packet writer, and {@link Roster} will not be removed; thus ++ * connection's state is kept. ++ * ++ * @param unavailablePresence the presence packet to send during shutdown. ++ */ ++ protected void shutdown(Presence unavailablePresence) { ++ // Set presence to offline. ++ if (packetWriter != null){ ++ packetWriter.sendPacket(unavailablePresence); ++ } ++ ++ this.setWasAuthenticated(authenticated); ++ authenticated = false; ++ ++ if (packetReader!=null){ ++ packetReader.shutdown(); ++ } ++ if (packetWriter!=null){ ++ packetWriter.shutdown(); ++ } ++ // Wait 150 ms for processes to clean-up, then shutdown. ++ try { ++ Thread.sleep(150); ++ } ++ catch (Exception e) { ++ // Ignore. ++ } ++ ++ socketClosed = true; ++ try { ++ socket.close(); ++ } catch (Exception e) { ++ e.printStackTrace(); ++ } ++ connected = false; ++ ++ ++ // Close down the readers and writers. ++ if (reader != null) { ++ try { ++ reader.close(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ reader = null; ++ } ++ if (writer != null) { ++ try { ++ writer.close(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ writer = null; ++ } ++ ++ // Make sure that the socket is really closed ++ try { ++ socket.close(); ++ } ++ catch (Exception e) { ++ // Ignore. ++ } ++ ++ saslAuthentication.init(); ++ } ++ ++ public synchronized void disconnect(Presence unavailablePresence) { ++ // If not connected, ignore this request. ++ PacketReader packetReader = this.packetReader; ++ PacketWriter packetWriter = this.packetWriter; ++ if (packetReader == null || packetWriter == null) { ++ return; ++ } ++ ++ shutdown(unavailablePresence); ++ ++ if (roster != null) { ++ roster.cleanup(); ++ roster = null; ++ } ++ chatManager = null; ++ ++ wasAuthenticated = false; ++ ++ packetWriter.cleanup(); ++ this.packetWriter = null; ++ packetReader.cleanup(); ++ this.packetReader = null; ++ } ++ ++ public void sendPacket(Packet packet) { ++ if (!isConnected()) { ++ throw new IllegalStateException("Not connected to server."); ++ } ++ if (packet == null) { ++ throw new NullPointerException("Packet is null."); ++ } ++ packetWriter.sendPacket(packet); ++ } ++ ++ /** ++ * Registers a packet interceptor with this connection. The interceptor will be ++ * invoked every time a packet is about to be sent by this connection. Interceptors ++ * may modify the packet to be sent. A packet filter determines which packets ++ * will be delivered to the interceptor. ++ * ++ * @param packetInterceptor the packet interceptor to notify of packets about to be sent. ++ * @param packetFilter the packet filter to use. ++ * @deprecated replaced by {@link Connection#addPacketInterceptor(PacketInterceptor, PacketFilter)}. ++ */ ++ public void addPacketWriterInterceptor(PacketInterceptor packetInterceptor, ++ PacketFilter packetFilter) { ++ addPacketInterceptor(packetInterceptor, packetFilter); ++ } ++ ++ /** ++ * Removes a packet interceptor. ++ * ++ * @param packetInterceptor the packet interceptor to remove. ++ * @deprecated replaced by {@link Connection#removePacketInterceptor(PacketInterceptor)}. ++ */ ++ public void removePacketWriterInterceptor(PacketInterceptor packetInterceptor) { ++ removePacketInterceptor(packetInterceptor); ++ } ++ ++ /** ++ * Registers a packet listener with this connection. The listener will be ++ * notified of every packet that this connection sends. A packet filter determines ++ * which packets will be delivered to the listener. Note that the thread ++ * that writes packets will be used to invoke the listeners. Therefore, each ++ * packet listener should complete all operations quickly or use a different ++ * thread for processing. ++ * ++ * @param packetListener the packet listener to notify of sent packets. ++ * @param packetFilter the packet filter to use. ++ * @deprecated replaced by {@link #addPacketSendingListener(PacketListener, PacketFilter)}. ++ */ ++ public void addPacketWriterListener(PacketListener packetListener, PacketFilter packetFilter) { ++ addPacketSendingListener(packetListener, packetFilter); ++ } ++ ++ /** ++ * Removes a packet listener for sending packets from this connection. ++ * ++ * @param packetListener the packet listener to remove. ++ * @deprecated replaced by {@link #removePacketSendingListener(PacketListener)}. ++ */ ++ public void removePacketWriterListener(PacketListener packetListener) { ++ removePacketSendingListener(packetListener); ++ } ++ ++ private void connectUsingConfiguration(ConnectionConfiguration config) throws XMPPException { ++ String host = config.getHost(); ++ int port = config.getPort(); ++ try { ++ if (config.getSocketFactory() == null) { ++ this.socket = new Socket(host, port); ++ } ++ else { ++ this.socket = config.getSocketFactory().createSocket(host, port); ++ } ++ } ++ catch (UnknownHostException uhe) { ++ String errorMessage = "Could not connect to " + host + ":" + port + "."; ++ throw new XMPPException(errorMessage, new XMPPError( ++ XMPPError.Condition.remote_server_timeout, errorMessage), ++ uhe); ++ } ++ catch (IOException ioe) { ++ String errorMessage = "XMPPError connecting to " + host + ":" ++ + port + "."; ++ throw new XMPPException(errorMessage, new XMPPError( ++ XMPPError.Condition.remote_server_error, errorMessage), ioe); ++ } ++ socketClosed = false; ++ initConnection(); ++ } ++ ++ /** ++ * Initializes the connection by creating a packet reader and writer and opening a ++ * XMPP stream to the server. ++ * ++ * @throws XMPPException if establishing a connection to the server fails. ++ */ ++ private void initConnection() throws XMPPException { ++ boolean isFirstInitialization = packetReader == null || packetWriter == null; ++ usingCompression = false; ++ ++ // Set the reader and writer instance variables ++ initReaderAndWriter(); ++ ++ try { ++ if (isFirstInitialization) { ++ packetWriter = new PacketWriter(this); ++ packetReader = new PacketReader(this); ++ ++ // If debugging is enabled, we should start the thread that will listen for ++ // all packets and then log them. ++ if (config.isDebuggerEnabled()) { ++ addPacketListener(debugger.getReaderListener(), null); ++ if (debugger.getWriterListener() != null) { ++ addPacketSendingListener(debugger.getWriterListener(), null); ++ } ++ } ++ } ++ else { ++ packetWriter.init(); ++ packetReader.init(); ++ } ++ ++ // Start the packet writer. This will open a XMPP stream to the server ++ packetWriter.startup(); ++ // Start the packet reader. The startup() method will block until we ++ // get an opening stream packet back from server. ++ packetReader.startup(); ++ ++ // Make note of the fact that we're now connected. ++ connected = true; ++ ++ // Start keep alive process (after TLS was negotiated - if available) ++ packetWriter.startKeepAliveProcess(); ++ ++ ++ if (isFirstInitialization) { ++ // Notify listeners that a new connection has been established ++ for (ConnectionCreationListener listener : getConnectionCreationListeners()) { ++ listener.connectionCreated(this); ++ } ++ } ++ else if (!wasAuthenticated) { ++ packetReader.notifyReconnection(); ++ } ++ ++ } ++ catch (XMPPException ex) { ++ // An exception occurred in setting up the connection. Make sure we shut down the ++ // readers and writers and close the socket. ++ ++ if (packetWriter != null) { ++ try { ++ packetWriter.shutdown(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ packetWriter = null; ++ } ++ if (packetReader != null) { ++ try { ++ packetReader.shutdown(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ packetReader = null; ++ } ++ if (reader != null) { ++ try { ++ reader.close(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ reader = null; ++ } ++ if (writer != null) { ++ try { ++ writer.close(); ++ } ++ catch (Throwable ignore) { /* ignore */} ++ writer = null; ++ } ++ if (socket != null) { ++ try { ++ socket.close(); ++ } ++ catch (Exception e) { /* ignore */ } ++ socket = null; ++ } ++ this.setWasAuthenticated(authenticated); ++ chatManager = null; ++ authenticated = false; ++ connected = false; ++ ++ throw ex; // Everything stoppped. Now throw the exception. ++ } ++ } ++ ++ private void initReaderAndWriter() throws XMPPException { ++ try { ++ if (!usingCompression) { ++ reader = ++ new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8")); ++ writer = new BufferedWriter( ++ new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); ++ } ++ else { ++ try { ++ Class zoClass = Class.forName("com.jcraft.jzlib.ZOutputStream"); ++ Constructor constructor = ++ zoClass.getConstructor(OutputStream.class, Integer.TYPE); ++ Object out = constructor.newInstance(socket.getOutputStream(), 9); ++ Method method = zoClass.getMethod("setFlushMode", Integer.TYPE); ++ method.invoke(out, 2); ++ writer = ++ new BufferedWriter(new OutputStreamWriter((OutputStream) out, "UTF-8")); ++ ++ Class ziClass = Class.forName("com.jcraft.jzlib.ZInputStream"); ++ constructor = ziClass.getConstructor(InputStream.class); ++ Object in = constructor.newInstance(socket.getInputStream()); ++ method = ziClass.getMethod("setFlushMode", Integer.TYPE); ++ method.invoke(in, 2); ++ reader = new BufferedReader(new InputStreamReader((InputStream) in, "UTF-8")); ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ reader = new BufferedReader( ++ new InputStreamReader(socket.getInputStream(), "UTF-8")); ++ writer = new BufferedWriter( ++ new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); ++ } ++ } ++ } ++ catch (IOException ioe) { ++ throw new XMPPException( ++ "XMPPError establishing connection with server.", ++ new XMPPError(XMPPError.Condition.remote_server_error, ++ "XMPPError establishing connection with server."), ++ ioe); ++ } ++ ++ // If debugging is enabled, we open a window and write out all network traffic. ++ initDebugger(); ++ } ++ ++ /*********************************************** ++ * TLS code below ++ **********************************************/ ++ ++ /** ++ * Returns true if the connection to the server has successfully negotiated TLS. Once TLS ++ * has been negotiatied the connection has been secured. ++ * ++ * @return true if the connection to the server has successfully negotiated TLS. ++ */ ++ public boolean isUsingTLS() { ++ return usingTLS; ++ } ++ ++ /** ++ * Notification message saying that the server supports TLS so confirm the server that we ++ * want to secure the connection. ++ * ++ * @param required true when the server indicates that TLS is required. ++ */ ++ void startTLSReceived(boolean required) { ++ if (required && config.getSecurityMode() == ++ ConnectionConfiguration.SecurityMode.disabled) { ++ packetReader.notifyConnectionError(new IllegalStateException( ++ "TLS required by server but not allowed by connection configuration")); ++ return; ++ } ++ ++ if (config.getSecurityMode() == ConnectionConfiguration.SecurityMode.disabled) { ++ // Do not secure the connection using TLS since TLS was disabled ++ return; ++ } ++ try { ++ writer.write(""); ++ writer.flush(); ++ } ++ catch (IOException e) { ++ packetReader.notifyConnectionError(e); ++ } ++ } ++ ++ /** ++ * The server has indicated that TLS negotiation can start. We now need to secure the ++ * existing plain connection and perform a handshake. This method won't return until the ++ * connection has finished the handshake or an error occured while securing the connection. ++ * ++ * @throws Exception if an exception occurs. ++ */ ++ void proceedTLSReceived() throws Exception { ++ SSLContext context = this.config.getCustomSSLContext(); ++ KeyStore ks = null; ++ KeyManager[] kms = null; ++ PasswordCallback pcb = null; ++ ++ if(config.getCallbackHandler() == null) { ++ ks = null; ++ } else if (context == null) { ++ //System.out.println("Keystore type: "+configuration.getKeystoreType()); ++ if(config.getKeystoreType().equals("NONE")) { ++ ks = null; ++ pcb = null; ++ } ++ else if(config.getKeystoreType().equals("PKCS11")) { ++ try { ++ Constructor c = Class.forName("sun.security.pkcs11.SunPKCS11").getConstructor(InputStream.class); ++ String pkcs11Config = "name = SmartCard\nlibrary = "+config.getPKCS11Library(); ++ ByteArrayInputStream config = new ByteArrayInputStream(pkcs11Config.getBytes()); ++ Provider p = (Provider)c.newInstance(config); ++ Security.addProvider(p); ++ ks = KeyStore.getInstance("PKCS11",p); ++ pcb = new PasswordCallback("PKCS11 Password: ",false); ++ this.config.getCallbackHandler().handle(new Callback[]{pcb}); ++ ks.load(null,pcb.getPassword()); ++ } ++ catch (Exception e) { ++ ks = null; ++ pcb = null; ++ } ++ } ++ else if(config.getKeystoreType().equals("Apple")) { ++ ks = KeyStore.getInstance("KeychainStore","Apple"); ++ ks.load(null,null); ++ //pcb = new PasswordCallback("Apple Keychain",false); ++ //pcb.setPassword(null); ++ } ++ else { ++ ks = KeyStore.getInstance(config.getKeystoreType()); ++ try { ++ pcb = new PasswordCallback("Keystore Password: ",false); ++ config.getCallbackHandler().handle(new Callback[]{pcb}); ++ ks.load(new FileInputStream(config.getKeystorePath()), pcb.getPassword()); ++ } ++ catch(Exception e) { ++ ks = null; ++ pcb = null; ++ } ++ } ++ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); ++ try { ++ if(pcb == null) { ++ kmf.init(ks,null); ++ } else { ++ kmf.init(ks,pcb.getPassword()); ++ pcb.clearPassword(); ++ } ++ kms = kmf.getKeyManagers(); ++ } catch (NullPointerException npe) { ++ kms = null; ++ } ++ } ++ ++ // Verify certificate presented by the server ++ if (context == null) { ++ context = SSLContext.getInstance("TLS"); ++ context.init(kms, ++ new javax.net.ssl.TrustManager[]{new ServerTrustManager(getServiceName(), config)}, ++ new java.security.SecureRandom()); ++ } ++ Socket plain = socket; ++ // Secure the plain connection ++ socket = context.getSocketFactory().createSocket(plain, ++ plain.getInetAddress().getHostName(), plain.getPort(), true); ++ socket.setSoTimeout(0); ++ socket.setKeepAlive(true); ++ // Initialize the reader and writer with the new secured version ++ initReaderAndWriter(); ++ // Proceed to do the handshake ++ ((SSLSocket) socket).startHandshake(); ++ //if (((SSLSocket) socket).getWantClientAuth()) { ++ // System.err.println("Connection wants client auth"); ++ //} ++ //else if (((SSLSocket) socket).getNeedClientAuth()) { ++ // System.err.println("Connection needs client auth"); ++ //} ++ //else { ++ // System.err.println("Connection does not require client auth"); ++ // } ++ // Set that TLS was successful ++ usingTLS = true; ++ ++ // Set the new writer to use ++ packetWriter.setWriter(writer); ++ // Send a new opening stream to the server ++ packetWriter.openStream(); ++ } ++ ++ /** ++ * Sets the available stream compression methods offered by the server. ++ * ++ * @param methods compression methods offered by the server. ++ */ ++ void setAvailableCompressionMethods(Collection methods) { ++ compressionMethods = methods; ++ } ++ ++ /** ++ * Returns true if the specified compression method was offered by the server. ++ * ++ * @param method the method to check. ++ * @return true if the specified compression method was offered by the server. ++ */ ++ private boolean hasAvailableCompressionMethod(String method) { ++ return compressionMethods != null && compressionMethods.contains(method); ++ } ++ ++ public boolean isUsingCompression() { ++ return usingCompression; ++ } ++ ++ /** ++ * Starts using stream compression that will compress network traffic. Traffic can be ++ * reduced up to 90%. Therefore, stream compression is ideal when using a slow speed network ++ * connection. However, the server and the client will need to use more CPU time in order to ++ * un/compress network data so under high load the server performance might be affected.

      ++ *

      ++ * Stream compression has to have been previously offered by the server. Currently only the ++ * zlib method is supported by the client. Stream compression negotiation has to be done ++ * before authentication took place.

      ++ *

      ++ * Note: to use stream compression the smackx.jar file has to be present in the classpath. ++ * ++ * @return true if stream compression negotiation was successful. ++ */ ++ private boolean useCompression() { ++ // If stream compression was offered by the server and we want to use ++ // compression then send compression request to the server ++ if (authenticated) { ++ throw new IllegalStateException("Compression should be negotiated before authentication."); ++ } ++ try { ++ Class.forName("com.jcraft.jzlib.ZOutputStream"); ++ } ++ catch (ClassNotFoundException e) { ++ throw new IllegalStateException("Cannot use compression. Add smackx.jar to the classpath"); ++ } ++ if (hasAvailableCompressionMethod("zlib")) { ++ requestStreamCompression(); ++ // Wait until compression is being used or a timeout happened ++ synchronized (this) { ++ try { ++ this.wait(SmackConfiguration.getPacketReplyTimeout() * 5); ++ } ++ catch (InterruptedException e) { ++ // Ignore. ++ } ++ } ++ return usingCompression; ++ } ++ return false; ++ } ++ ++ /** ++ * Request the server that we want to start using stream compression. When using TLS ++ * then negotiation of stream compression can only happen after TLS was negotiated. If TLS ++ * compression is being used the stream compression should not be used. ++ */ ++ private void requestStreamCompression() { ++ try { ++ writer.write(""); ++ writer.write("zlib"); ++ writer.flush(); ++ } ++ catch (IOException e) { ++ packetReader.notifyConnectionError(e); ++ } ++ } ++ ++ /** ++ * Start using stream compression since the server has acknowledged stream compression. ++ * ++ * @throws Exception if there is an exception starting stream compression. ++ */ ++ void startStreamCompression() throws Exception { ++ // Secure the plain connection ++ usingCompression = true; ++ // Initialize the reader and writer with the new secured version ++ initReaderAndWriter(); ++ ++ // Set the new writer to use ++ packetWriter.setWriter(writer); ++ // Send a new opening stream to the server ++ packetWriter.openStream(); ++ // Notify that compression is being used ++ synchronized (this) { ++ this.notify(); ++ } ++ } ++ ++ /** ++ * Notifies the XMPP connection that stream compression was denied so that ++ * the connection process can proceed. ++ */ ++ void streamCompressionDenied() { ++ synchronized (this) { ++ this.notify(); ++ } ++ } ++ ++ /** ++ * Establishes a connection to the XMPP server and performs an automatic login ++ * only if the previous connection state was logged (authenticated). It basically ++ * creates and maintains a socket connection to the server.

      ++ *

      ++ * Listeners will be preserved from a previous connection if the reconnection ++ * occurs after an abrupt termination. ++ * ++ * @throws XMPPException if an error occurs while trying to establish the connection. ++ * Two possible errors can occur which will be wrapped by an XMPPException -- ++ * UnknownHostException (XMPP error code 504), and IOException (XMPP error code ++ * 502). The error codes and wrapped exceptions can be used to present more ++ * appropiate error messages to end-users. ++ */ ++ public void connect() throws XMPPException { ++ connect(true); ++ } ++ ++ public void connect(boolean bind) throws XMPPException { ++ // Establishes the connection, readers and writers ++ connectUsingConfiguration(config); ++ // Automatically makes the login if the user was previouslly connected successfully ++ // to the server and the connection was terminated abruptly ++ if (connected && wasAuthenticated) { ++ // Make the login ++ try { ++ if (isAnonymous()) { ++ // Make the anonymous login ++ loginAnonymously(); ++ } ++ else { ++ login(config.getUsername(), config.getPassword(), ++ bind ? config.getResource() : null); ++ } ++ packetReader.notifyReconnection(); ++ } ++ catch (XMPPException e) { ++ e.printStackTrace(); ++ } ++ } ++ } ++ ++ /** ++ * Sets whether the connection has already logged in the server. ++ * ++ * @param wasAuthenticated true if the connection has already been authenticated. ++ */ ++ private void setWasAuthenticated(boolean wasAuthenticated) { ++ if (!this.wasAuthenticated) { ++ this.wasAuthenticated = wasAuthenticated; ++ } ++ } ++ ++ @Override ++ public void setRosterStorage(RosterStorage storage) ++ throws IllegalStateException { ++ if(roster!=null){ ++ throw new IllegalStateException("Roster is already initialized"); ++ } ++ this.rosterStorage = storage; ++ } ++ ++ public boolean isEntityCapsEnabled() { ++ return config.isEntityCapsEnabled(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPConnection.java.orig b/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPConnection.java.orig +new file mode 100644 +index 0000000..cb49b3d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPConnection.java.orig +@@ -0,0 +1,1099 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.Presence; ++import org.jivesoftware.smack.packet.XMPPError; ++import org.jivesoftware.smack.util.StringUtils; ++ ++import javax.net.ssl.KeyManager; ++import javax.net.ssl.KeyManagerFactory; ++import javax.net.ssl.SSLContext; ++import javax.net.ssl.SSLSocket; ++import org.apache.harmony.javax.security.auth.callback.Callback; ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++import org.apache.harmony.javax.security.auth.callback.PasswordCallback; ++import java.io.*; ++import java.lang.reflect.Constructor; ++import java.lang.reflect.Method; ++import java.net.Socket; ++import java.net.UnknownHostException; ++import java.security.KeyStore; ++import java.security.Provider; ++import java.security.Security; ++import java.util.Collection; ++ ++/** ++ * Creates a socket connection to a XMPP server. This is the default connection ++ * to a Jabber server and is specified in the XMPP Core (RFC 3920). ++ * ++ * @see Connection ++ * @author Matt Tucker ++ */ ++public class XMPPConnection extends Connection { ++ ++ /** ++ * The socket which is used for this connection. ++ */ ++ Socket socket; ++ ++ private String user = null; ++ protected boolean connected = false; ++ private boolean socketClosed = false; ++ /** ++ * Flag that indicates if the user is currently authenticated with the server. ++ */ ++ private boolean authenticated = false; ++ /** ++ * Flag that indicates if the user was authenticated with the server when the connection ++ * to the server was closed (abruptly or not). ++ */ ++ private boolean wasAuthenticated = false; ++ private boolean anonymous = false; ++ private boolean usingTLS = false; ++ ++ PacketWriter packetWriter; ++ PacketReader packetReader; ++ ++ Roster roster = null; ++ ++ /** ++ * Collection of available stream compression methods offered by the server. ++ */ ++ private Collection compressionMethods; ++ /** ++ * Flag that indicates if stream compression is actually in use. ++ */ ++ private boolean usingCompression; ++ ++ ++ /** ++ * Creates a new connection to the specified XMPP server. A DNS SRV lookup will be ++ * performed to determine the IP address and port corresponding to the ++ * service name; if that lookup fails, it's assumed that server resides at ++ * serviceName with the default port of 5222. Encrypted connections (TLS) ++ * will be used if available, stream compression is disabled, and standard SASL ++ * mechanisms will be used for authentication.

      ++ *

      ++ * This is the simplest constructor for connecting to an XMPP server. Alternatively, ++ * you can get fine-grained control over connection settings using the ++ * {@link #XMPPConnection(ConnectionConfiguration)} constructor.

      ++ *

      ++ * Note that XMPPConnection constructors do not establish a connection to the server ++ * and you must call {@link #connect()}.

      ++ *

      ++ * The CallbackHandler will only be used if the connection requires the client provide ++ * an SSL certificate to the server. The CallbackHandler must handle the PasswordCallback ++ * to prompt for a password to unlock the keystore containing the SSL certificate. ++ * ++ * @param serviceName the name of the XMPP server to connect to; e.g. example.com. ++ * @param callbackHandler the CallbackHandler used to prompt for the password to the keystore. ++ */ ++ public XMPPConnection(String serviceName, CallbackHandler callbackHandler) { ++ // Create the configuration for this new connection ++ super(new ConnectionConfiguration(serviceName)); ++ config.setCompressionEnabled(false); ++ config.setSASLAuthenticationEnabled(true); ++ config.setDebuggerEnabled(DEBUG_ENABLED); ++ config.setCallbackHandler(callbackHandler); ++ } ++ ++ /** ++ * Creates a new XMPP connection in the same way {@link #XMPPConnection(String,CallbackHandler)} does, but ++ * with no callback handler for password prompting of the keystore. This will work ++ * in most cases, provided the client is not required to provide a certificate to ++ * the server. ++ * ++ * @param serviceName the name of the XMPP server to connect to; e.g. example.com. ++ */ ++ public XMPPConnection(String serviceName) { ++ // Create the configuration for this new connection ++ super(new ConnectionConfiguration(serviceName)); ++ config.setCompressionEnabled(false); ++ config.setSASLAuthenticationEnabled(true); ++ config.setDebuggerEnabled(DEBUG_ENABLED); ++ } ++ ++ /** ++ * Creates a new XMPP connection in the same way {@link #XMPPConnection(ConnectionConfiguration,CallbackHandler)} does, but ++ * with no callback handler for password prompting of the keystore. This will work ++ * in most cases, provided the client is not required to provide a certificate to ++ * the server. ++ * ++ * ++ * @param config the connection configuration. ++ */ ++ public XMPPConnection(ConnectionConfiguration config) { ++ super(config); ++ } ++ ++ /** ++ * Creates a new XMPP connection using the specified connection configuration.

      ++ *

      ++ * Manually specifying connection configuration information is suitable for ++ * advanced users of the API. In many cases, using the ++ * {@link #XMPPConnection(String)} constructor is a better approach.

      ++ *

      ++ * Note that XMPPConnection constructors do not establish a connection to the server ++ * and you must call {@link #connect()}.

      ++ *

      ++ * ++ * The CallbackHandler will only be used if the connection requires the client provide ++ * an SSL certificate to the server. The CallbackHandler must handle the PasswordCallback ++ * to prompt for a password to unlock the keystore containing the SSL certificate. ++ * ++ * @param config the connection configuration. ++ * @param callbackHandler the CallbackHandler used to prompt for the password to the keystore. ++ */ ++ public XMPPConnection(ConnectionConfiguration config, CallbackHandler callbackHandler) { ++ super(config); ++ config.setCallbackHandler(callbackHandler); ++ } ++ ++ public String getConnectionID() { ++ if (!isConnected()) { ++ return null; ++ } ++ return connectionID; ++ } ++ ++ public String getUser() { ++ if (!isAuthenticated()) { ++ return null; ++ } ++ return user; ++ } ++ ++ @Override ++ public synchronized void login(String username, String password, String resource) throws XMPPException { ++ if (!isConnected()) { ++ throw new IllegalStateException("Not connected to server."); ++ } ++ if (authenticated) { ++ throw new IllegalStateException("Already logged in to server."); ++ } ++ // Do partial version of nameprep on the username. ++ username = username.toLowerCase().trim(); ++ ++ String response; ++ if (config.isSASLAuthenticationEnabled() && ++ saslAuthentication.hasNonAnonymousAuthentication()) { ++ // Authenticate using SASL ++ if (password != null) { ++ response = saslAuthentication.authenticate(username, password, resource); ++ } ++ else { ++ response = saslAuthentication ++ .authenticate(username, resource, config.getCallbackHandler()); ++ } ++ } ++ else { ++ // Authenticate using Non-SASL ++ response = new NonSASLAuthentication(this).authenticate(username, password, resource); ++ } ++ ++ // If compression is enabled then request the server to use stream compression ++ if (config.isCompressionEnabled()) { ++ useCompression(); ++ } ++ ++ // Indicate that we're now authenticated. ++ authenticated = true; ++ anonymous = false; ++ ++ // If we did not bind, don't attempt to do anything with roster or presence ++ if (resource == null) ++ return; ++ ++ // Set the user. ++ if (response != null) { ++ this.user = response; ++ // Update the serviceName with the one returned by the server ++ config.setServiceName(StringUtils.parseServer(response)); ++ } ++ else { ++ this.user = username + "@" + getServiceName(); ++ if (resource != null) { ++ this.user += "/" + resource; ++ } ++ } ++ ++ // Create the roster if it is not a reconnection or roster already created by getRoster() ++ if (this.roster == null) { ++ if(rosterStorage==null){ ++ this.roster = new Roster(this); ++ } ++ else{ ++ this.roster = new Roster(this,rosterStorage); ++ } ++ } ++ if (config.isRosterLoadedAtLogin()) { ++ this.roster.reload(); ++ } ++ ++ // Set presence to online. ++ if (config.isSendPresence()) { ++ packetWriter.sendPacket(new Presence(Presence.Type.available)); ++ } ++ ++ // Stores the authentication for future reconnection ++ config.setLoginInfo(username, password, resource); ++ ++ // If debugging is enabled, change the the debug window title to include the ++ // name we are now logged-in as. ++ // If DEBUG_ENABLED was set to true AFTER the connection was created the debugger ++ // will be null ++ if (config.isDebuggerEnabled() && debugger != null) { ++ debugger.userHasLogged(user); ++ } ++ } ++ ++ @Override ++ public synchronized void loginAnonymously() throws XMPPException { ++ if (!isConnected()) { ++ throw new IllegalStateException("Not connected to server."); ++ } ++ if (authenticated) { ++ throw new IllegalStateException("Already logged in to server."); ++ } ++ ++ String response; ++ if (config.isSASLAuthenticationEnabled() && ++ saslAuthentication.hasAnonymousAuthentication()) { ++ response = saslAuthentication.authenticateAnonymously(); ++ } ++ else { ++ // Authenticate using Non-SASL ++ response = new NonSASLAuthentication(this).authenticateAnonymously(); ++ } ++ ++ // Set the user value. ++ this.user = response; ++ // Update the serviceName with the one returned by the server ++ config.setServiceName(StringUtils.parseServer(response)); ++ ++ // If compression is enabled then request the server to use stream compression ++ if (config.isCompressionEnabled()) { ++ useCompression(); ++ } ++ ++ // Set presence to online. ++ packetWriter.sendPacket(new Presence(Presence.Type.available)); ++ ++ // Indicate that we're now authenticated. ++ authenticated = true; ++ anonymous = true; ++ ++ // If debugging is enabled, change the the debug window title to include the ++ // name we are now logged-in as. ++ // If DEBUG_ENABLED was set to true AFTER the connection was created the debugger ++ // will be null ++ if (config.isDebuggerEnabled() && debugger != null) { ++ debugger.userHasLogged(user); ++ } ++ } ++ ++ public Roster getRoster() { ++ // synchronize against login() ++ synchronized(this) { ++ // if connection is authenticated the roster is already set by login() ++ // or a previous call to getRoster() ++ if (!isAuthenticated() || isAnonymous()) { ++ if (roster == null) { ++ roster = new Roster(this); ++ } ++ return roster; ++ } ++ } ++ ++ if (!config.isRosterLoadedAtLogin()) { ++ roster.reload(); ++ } ++ // If this is the first time the user has asked for the roster after calling ++ // login, we want to wait for the server to send back the user's roster. This ++ // behavior shields API users from having to worry about the fact that roster ++ // operations are asynchronous, although they'll still have to listen for ++ // changes to the roster. Note: because of this waiting logic, internal ++ // Smack code should be wary about calling the getRoster method, and may need to ++ // access the roster object directly. ++ if (!roster.rosterInitialized) { ++ try { ++ synchronized (roster) { ++ long waitTime = SmackConfiguration.getPacketReplyTimeout(); ++ long start = System.currentTimeMillis(); ++ while (!roster.rosterInitialized) { ++ if (waitTime <= 0) { ++ break; ++ } ++ roster.wait(waitTime); ++ long now = System.currentTimeMillis(); ++ waitTime -= now - start; ++ start = now; ++ } ++ } ++ } ++ catch (InterruptedException ie) { ++ // Ignore. ++ } ++ } ++ return roster; ++ } ++ ++ public boolean isConnected() { ++ return connected; ++ } ++ ++ public boolean isSecureConnection() { ++ return isUsingTLS(); ++ } ++ ++ public boolean isSocketClosed() { ++ return socketClosed; ++ } ++ ++ public boolean isAuthenticated() { ++ return authenticated; ++ } ++ ++ public boolean isAnonymous() { ++ return anonymous; ++ } ++ ++ /** ++ * Forceful shutdown without full disconnect. ++ * ++ *

      Used when a reconnection is possible. ++ */ ++ public void quickShutdown() { ++ try { ++ this.setWasAuthenticated(authenticated); ++ authenticated = false; ++ ++ // Close socket before we close writer, so that stream end is not sent and reconnection is possible. ++ // This may cause a ConnectionClosedOnError notification to go out. ++ socketClosed = true; ++ try { ++ socket.shutdownInput(); ++ } ++ catch (Exception e) { ++ } ++ try { ++ socket.close(); ++ } catch (Exception e) { ++ } ++ ++ if (packetReader!=null){ ++ // Does not notify listeners ++ packetReader.quickShutdown(); ++ } ++ if (packetWriter!=null){ ++ packetWriter.shutdown(); ++ } ++ // Wait 150 ms for processes to clean-up, then shutdown. ++ try { ++ Thread.sleep(150); ++ } ++ catch (Exception e) { ++ // Ignore. ++ } ++ ++ connected = false; ++ ++ // Close down the readers and writers. ++ if (reader != null) { ++ try { ++ reader.close(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ reader = null; ++ } ++ if (writer != null) { ++ try { ++ writer.close(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ writer = null; ++ } ++ ++ saslAuthentication.init(); ++ } ++ catch (Exception e) { ++ System.err.println(e); ++ } ++ } ++ ++ /** ++ * Closes the connection by setting presence to unavailable then closing the stream to ++ * the XMPP server. The shutdown logic will be used during a planned disconnection or when ++ * dealing with an unexpected disconnection. Unlike {@link #disconnect()} the connection's ++ * packet reader, packet writer, and {@link Roster} will not be removed; thus ++ * connection's state is kept. ++ * ++ * @param unavailablePresence the presence packet to send during shutdown. ++ */ ++ protected void shutdown(Presence unavailablePresence) { ++ // Set presence to offline. ++ if (packetWriter != null){ ++ packetWriter.sendPacket(unavailablePresence); ++ } ++ ++ this.setWasAuthenticated(authenticated); ++ authenticated = false; ++ ++ if (packetReader!=null){ ++ packetReader.shutdown(); ++ } ++ if (packetWriter!=null){ ++ packetWriter.shutdown(); ++ } ++ // Wait 150 ms for processes to clean-up, then shutdown. ++ try { ++ Thread.sleep(150); ++ } ++ catch (Exception e) { ++ // Ignore. ++ } ++ ++ socketClosed = true; ++ try { ++ socket.close(); ++ } catch (Exception e) { ++ e.printStackTrace(); ++ } ++ connected = false; ++ ++ ++ // Close down the readers and writers. ++ if (reader != null) { ++ try { ++ reader.close(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ reader = null; ++ } ++ if (writer != null) { ++ try { ++ writer.close(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ writer = null; ++ } ++ ++ // Make sure that the socket is really closed ++ try { ++ socket.close(); ++ } ++ catch (Exception e) { ++ // Ignore. ++ } ++ ++ saslAuthentication.init(); ++ } ++ ++ public synchronized void disconnect(Presence unavailablePresence) { ++ // If not connected, ignore this request. ++ PacketReader packetReader = this.packetReader; ++ PacketWriter packetWriter = this.packetWriter; ++ if (packetReader == null || packetWriter == null) { ++ return; ++ } ++ ++ shutdown(unavailablePresence); ++ ++ if (roster != null) { ++ roster.cleanup(); ++ roster = null; ++ } ++ chatManager = null; ++ ++ wasAuthenticated = false; ++ ++ packetWriter.cleanup(); ++ this.packetWriter = null; ++ packetReader.cleanup(); ++ this.packetReader = null; ++ } ++ ++ public void sendPacket(Packet packet) { ++ if (!isConnected()) { ++ throw new IllegalStateException("Not connected to server."); ++ } ++ if (packet == null) { ++ throw new NullPointerException("Packet is null."); ++ } ++ packetWriter.sendPacket(packet); ++ } ++ ++ /** ++ * Registers a packet interceptor with this connection. The interceptor will be ++ * invoked every time a packet is about to be sent by this connection. Interceptors ++ * may modify the packet to be sent. A packet filter determines which packets ++ * will be delivered to the interceptor. ++ * ++ * @param packetInterceptor the packet interceptor to notify of packets about to be sent. ++ * @param packetFilter the packet filter to use. ++ * @deprecated replaced by {@link Connection#addPacketInterceptor(PacketInterceptor, PacketFilter)}. ++ */ ++ public void addPacketWriterInterceptor(PacketInterceptor packetInterceptor, ++ PacketFilter packetFilter) { ++ addPacketInterceptor(packetInterceptor, packetFilter); ++ } ++ ++ /** ++ * Removes a packet interceptor. ++ * ++ * @param packetInterceptor the packet interceptor to remove. ++ * @deprecated replaced by {@link Connection#removePacketInterceptor(PacketInterceptor)}. ++ */ ++ public void removePacketWriterInterceptor(PacketInterceptor packetInterceptor) { ++ removePacketInterceptor(packetInterceptor); ++ } ++ ++ /** ++ * Registers a packet listener with this connection. The listener will be ++ * notified of every packet that this connection sends. A packet filter determines ++ * which packets will be delivered to the listener. Note that the thread ++ * that writes packets will be used to invoke the listeners. Therefore, each ++ * packet listener should complete all operations quickly or use a different ++ * thread for processing. ++ * ++ * @param packetListener the packet listener to notify of sent packets. ++ * @param packetFilter the packet filter to use. ++ * @deprecated replaced by {@link #addPacketSendingListener(PacketListener, PacketFilter)}. ++ */ ++ public void addPacketWriterListener(PacketListener packetListener, PacketFilter packetFilter) { ++ addPacketSendingListener(packetListener, packetFilter); ++ } ++ ++ /** ++ * Removes a packet listener for sending packets from this connection. ++ * ++ * @param packetListener the packet listener to remove. ++ * @deprecated replaced by {@link #removePacketSendingListener(PacketListener)}. ++ */ ++ public void removePacketWriterListener(PacketListener packetListener) { ++ removePacketSendingListener(packetListener); ++ } ++ ++ private void connectUsingConfiguration(ConnectionConfiguration config) throws XMPPException { ++ String host = config.getHost(); ++ int port = config.getPort(); ++ try { ++ if (config.getSocketFactory() == null) { ++ this.socket = new Socket(host, port); ++ } ++ else { ++ this.socket = config.getSocketFactory().createSocket(host, port); ++ } ++ } ++ catch (UnknownHostException uhe) { ++ String errorMessage = "Could not connect to " + host + ":" + port + "."; ++ throw new XMPPException(errorMessage, new XMPPError( ++ XMPPError.Condition.remote_server_timeout, errorMessage), ++ uhe); ++ } ++ catch (IOException ioe) { ++ String errorMessage = "XMPPError connecting to " + host + ":" ++ + port + "."; ++ throw new XMPPException(errorMessage, new XMPPError( ++ XMPPError.Condition.remote_server_error, errorMessage), ioe); ++ } ++ socketClosed = false; ++ initConnection(); ++ } ++ ++ /** ++ * Initializes the connection by creating a packet reader and writer and opening a ++ * XMPP stream to the server. ++ * ++ * @throws XMPPException if establishing a connection to the server fails. ++ */ ++ private void initConnection() throws XMPPException { ++ boolean isFirstInitialization = packetReader == null || packetWriter == null; ++ usingCompression = false; ++ ++ // Set the reader and writer instance variables ++ initReaderAndWriter(); ++ ++ try { ++ if (isFirstInitialization) { ++ packetWriter = new PacketWriter(this); ++ packetReader = new PacketReader(this); ++ ++ // If debugging is enabled, we should start the thread that will listen for ++ // all packets and then log them. ++ if (config.isDebuggerEnabled()) { ++ addPacketListener(debugger.getReaderListener(), null); ++ if (debugger.getWriterListener() != null) { ++ addPacketSendingListener(debugger.getWriterListener(), null); ++ } ++ } ++ } ++ else { ++ packetWriter.init(); ++ packetReader.init(); ++ } ++ ++ // Start the packet writer. This will open a XMPP stream to the server ++ packetWriter.startup(); ++ // Start the packet reader. The startup() method will block until we ++ // get an opening stream packet back from server. ++ packetReader.startup(); ++ ++ // Make note of the fact that we're now connected. ++ connected = true; ++ ++ // Start keep alive process (after TLS was negotiated - if available) ++ packetWriter.startKeepAliveProcess(); ++ ++ ++ if (isFirstInitialization) { ++ // Notify listeners that a new connection has been established ++ for (ConnectionCreationListener listener : getConnectionCreationListeners()) { ++ listener.connectionCreated(this); ++ } ++ } ++ else if (!wasAuthenticated) { ++ packetReader.notifyReconnection(); ++ } ++ ++ } ++ catch (XMPPException ex) { ++ // An exception occurred in setting up the connection. Make sure we shut down the ++ // readers and writers and close the socket. ++ ++ if (packetWriter != null) { ++ try { ++ packetWriter.shutdown(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ packetWriter = null; ++ } ++ if (packetReader != null) { ++ try { ++ packetReader.shutdown(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ packetReader = null; ++ } ++ if (reader != null) { ++ try { ++ reader.close(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ reader = null; ++ } ++ if (writer != null) { ++ try { ++ writer.close(); ++ } ++ catch (Throwable ignore) { /* ignore */} ++ writer = null; ++ } ++ if (socket != null) { ++ try { ++ socket.close(); ++ } ++ catch (Exception e) { /* ignore */ } ++ socket = null; ++ } ++ this.setWasAuthenticated(authenticated); ++ chatManager = null; ++ authenticated = false; ++ connected = false; ++ ++ throw ex; // Everything stoppped. Now throw the exception. ++ } ++ } ++ ++ private void initReaderAndWriter() throws XMPPException { ++ try { ++ if (!usingCompression) { ++ reader = ++ new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8")); ++ writer = new BufferedWriter( ++ new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); ++ } ++ else { ++ try { ++ Class zoClass = Class.forName("com.jcraft.jzlib.ZOutputStream"); ++ Constructor constructor = ++ zoClass.getConstructor(OutputStream.class, Integer.TYPE); ++ Object out = constructor.newInstance(socket.getOutputStream(), 9); ++ Method method = zoClass.getMethod("setFlushMode", Integer.TYPE); ++ method.invoke(out, 2); ++ writer = ++ new BufferedWriter(new OutputStreamWriter((OutputStream) out, "UTF-8")); ++ ++ Class ziClass = Class.forName("com.jcraft.jzlib.ZInputStream"); ++ constructor = ziClass.getConstructor(InputStream.class); ++ Object in = constructor.newInstance(socket.getInputStream()); ++ method = ziClass.getMethod("setFlushMode", Integer.TYPE); ++ method.invoke(in, 2); ++ reader = new BufferedReader(new InputStreamReader((InputStream) in, "UTF-8")); ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ reader = new BufferedReader( ++ new InputStreamReader(socket.getInputStream(), "UTF-8")); ++ writer = new BufferedWriter( ++ new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); ++ } ++ } ++ } ++ catch (IOException ioe) { ++ throw new XMPPException( ++ "XMPPError establishing connection with server.", ++ new XMPPError(XMPPError.Condition.remote_server_error, ++ "XMPPError establishing connection with server."), ++ ioe); ++ } ++ ++ // If debugging is enabled, we open a window and write out all network traffic. ++ initDebugger(); ++ } ++ ++ /*********************************************** ++ * TLS code below ++ **********************************************/ ++ ++ /** ++ * Returns true if the connection to the server has successfully negotiated TLS. Once TLS ++ * has been negotiatied the connection has been secured. ++ * ++ * @return true if the connection to the server has successfully negotiated TLS. ++ */ ++ public boolean isUsingTLS() { ++ return usingTLS; ++ } ++ ++ /** ++ * Notification message saying that the server supports TLS so confirm the server that we ++ * want to secure the connection. ++ * ++ * @param required true when the server indicates that TLS is required. ++ */ ++ void startTLSReceived(boolean required) { ++ if (required && config.getSecurityMode() == ++ ConnectionConfiguration.SecurityMode.disabled) { ++ packetReader.notifyConnectionError(new IllegalStateException( ++ "TLS required by server but not allowed by connection configuration")); ++ return; ++ } ++ ++ if (config.getSecurityMode() == ConnectionConfiguration.SecurityMode.disabled) { ++ // Do not secure the connection using TLS since TLS was disabled ++ return; ++ } ++ try { ++ writer.write(""); ++ writer.flush(); ++ } ++ catch (IOException e) { ++ packetReader.notifyConnectionError(e); ++ } ++ } ++ ++ /** ++ * The server has indicated that TLS negotiation can start. We now need to secure the ++ * existing plain connection and perform a handshake. This method won't return until the ++ * connection has finished the handshake or an error occured while securing the connection. ++ * ++ * @throws Exception if an exception occurs. ++ */ ++ void proceedTLSReceived() throws Exception { ++ SSLContext context = this.config.getCustomSSLContext(); ++ KeyStore ks = null; ++ KeyManager[] kms = null; ++ PasswordCallback pcb = null; ++ ++ if(config.getCallbackHandler() == null) { ++ ks = null; ++ } else if (context == null) { ++ //System.out.println("Keystore type: "+configuration.getKeystoreType()); ++ if(config.getKeystoreType().equals("NONE")) { ++ ks = null; ++ pcb = null; ++ } ++ else if(config.getKeystoreType().equals("PKCS11")) { ++ try { ++ Constructor c = Class.forName("sun.security.pkcs11.SunPKCS11").getConstructor(InputStream.class); ++ String pkcs11Config = "name = SmartCard\nlibrary = "+config.getPKCS11Library(); ++ ByteArrayInputStream config = new ByteArrayInputStream(pkcs11Config.getBytes()); ++ Provider p = (Provider)c.newInstance(config); ++ Security.addProvider(p); ++ ks = KeyStore.getInstance("PKCS11",p); ++ pcb = new PasswordCallback("PKCS11 Password: ",false); ++ this.config.getCallbackHandler().handle(new Callback[]{pcb}); ++ ks.load(null,pcb.getPassword()); ++ } ++ catch (Exception e) { ++ ks = null; ++ pcb = null; ++ } ++ } ++ else if(config.getKeystoreType().equals("Apple")) { ++ ks = KeyStore.getInstance("KeychainStore","Apple"); ++ ks.load(null,null); ++ //pcb = new PasswordCallback("Apple Keychain",false); ++ //pcb.setPassword(null); ++ } ++ else { ++ ks = KeyStore.getInstance(config.getKeystoreType()); ++ try { ++ pcb = new PasswordCallback("Keystore Password: ",false); ++ config.getCallbackHandler().handle(new Callback[]{pcb}); ++ ks.load(new FileInputStream(config.getKeystorePath()), pcb.getPassword()); ++ } ++ catch(Exception e) { ++ ks = null; ++ pcb = null; ++ } ++ } ++ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); ++ try { ++ if(pcb == null) { ++ kmf.init(ks,null); ++ } else { ++ kmf.init(ks,pcb.getPassword()); ++ pcb.clearPassword(); ++ } ++ kms = kmf.getKeyManagers(); ++ } catch (NullPointerException npe) { ++ kms = null; ++ } ++ } ++ ++ // Verify certificate presented by the server ++ if (context == null) { ++ context = SSLContext.getInstance("TLS"); ++ context.init(kms, ++ new javax.net.ssl.TrustManager[]{new ServerTrustManager(getServiceName(), config)}, ++ new java.security.SecureRandom()); ++ } ++ Socket plain = socket; ++ // Secure the plain connection ++ socket = context.getSocketFactory().createSocket(plain, ++ plain.getInetAddress().getHostName(), plain.getPort(), true); ++ socket.setSoTimeout(0); ++ socket.setKeepAlive(true); ++ // Initialize the reader and writer with the new secured version ++ initReaderAndWriter(); ++ // Proceed to do the handshake ++ ((SSLSocket) socket).startHandshake(); ++ //if (((SSLSocket) socket).getWantClientAuth()) { ++ // System.err.println("Connection wants client auth"); ++ //} ++ //else if (((SSLSocket) socket).getNeedClientAuth()) { ++ // System.err.println("Connection needs client auth"); ++ //} ++ //else { ++ // System.err.println("Connection does not require client auth"); ++ // } ++ // Set that TLS was successful ++ usingTLS = true; ++ ++ // Set the new writer to use ++ packetWriter.setWriter(writer); ++ // Send a new opening stream to the server ++ packetWriter.openStream(); ++ } ++ ++ /** ++ * Sets the available stream compression methods offered by the server. ++ * ++ * @param methods compression methods offered by the server. ++ */ ++ void setAvailableCompressionMethods(Collection methods) { ++ compressionMethods = methods; ++ } ++ ++ /** ++ * Returns true if the specified compression method was offered by the server. ++ * ++ * @param method the method to check. ++ * @return true if the specified compression method was offered by the server. ++ */ ++ private boolean hasAvailableCompressionMethod(String method) { ++ return compressionMethods != null && compressionMethods.contains(method); ++ } ++ ++ public boolean isUsingCompression() { ++ return usingCompression; ++ } ++ ++ /** ++ * Starts using stream compression that will compress network traffic. Traffic can be ++ * reduced up to 90%. Therefore, stream compression is ideal when using a slow speed network ++ * connection. However, the server and the client will need to use more CPU time in order to ++ * un/compress network data so under high load the server performance might be affected.

      ++ *

      ++ * Stream compression has to have been previously offered by the server. Currently only the ++ * zlib method is supported by the client. Stream compression negotiation has to be done ++ * before authentication took place.

      ++ *

      ++ * Note: to use stream compression the smackx.jar file has to be present in the classpath. ++ * ++ * @return true if stream compression negotiation was successful. ++ */ ++ private boolean useCompression() { ++ // If stream compression was offered by the server and we want to use ++ // compression then send compression request to the server ++ if (authenticated) { ++ throw new IllegalStateException("Compression should be negotiated before authentication."); ++ } ++ try { ++ Class.forName("com.jcraft.jzlib.ZOutputStream"); ++ } ++ catch (ClassNotFoundException e) { ++ throw new IllegalStateException("Cannot use compression. Add smackx.jar to the classpath"); ++ } ++ if (hasAvailableCompressionMethod("zlib")) { ++ requestStreamCompression(); ++ // Wait until compression is being used or a timeout happened ++ synchronized (this) { ++ try { ++ this.wait(SmackConfiguration.getPacketReplyTimeout() * 5); ++ } ++ catch (InterruptedException e) { ++ // Ignore. ++ } ++ } ++ return usingCompression; ++ } ++ return false; ++ } ++ ++ /** ++ * Request the server that we want to start using stream compression. When using TLS ++ * then negotiation of stream compression can only happen after TLS was negotiated. If TLS ++ * compression is being used the stream compression should not be used. ++ */ ++ private void requestStreamCompression() { ++ try { ++ writer.write(""); ++ writer.write("zlib"); ++ writer.flush(); ++ } ++ catch (IOException e) { ++ packetReader.notifyConnectionError(e); ++ } ++ } ++ ++ /** ++ * Start using stream compression since the server has acknowledged stream compression. ++ * ++ * @throws Exception if there is an exception starting stream compression. ++ */ ++ void startStreamCompression() throws Exception { ++ // Secure the plain connection ++ usingCompression = true; ++ // Initialize the reader and writer with the new secured version ++ initReaderAndWriter(); ++ ++ // Set the new writer to use ++ packetWriter.setWriter(writer); ++ // Send a new opening stream to the server ++ packetWriter.openStream(); ++ // Notify that compression is being used ++ synchronized (this) { ++ this.notify(); ++ } ++ } ++ ++ /** ++ * Notifies the XMPP connection that stream compression was denied so that ++ * the connection process can proceed. ++ */ ++ void streamCompressionDenied() { ++ synchronized (this) { ++ this.notify(); ++ } ++ } ++ ++ /** ++ * Establishes a connection to the XMPP server and performs an automatic login ++ * only if the previous connection state was logged (authenticated). It basically ++ * creates and maintains a socket connection to the server.

      ++ *

      ++ * Listeners will be preserved from a previous connection if the reconnection ++ * occurs after an abrupt termination. ++ * ++ * @throws XMPPException if an error occurs while trying to establish the connection. ++ * Two possible errors can occur which will be wrapped by an XMPPException -- ++ * UnknownHostException (XMPP error code 504), and IOException (XMPP error code ++ * 502). The error codes and wrapped exceptions can be used to present more ++ * appropiate error messages to end-users. ++ */ ++ public void connect() throws XMPPException { ++ connect(true); ++ } ++ ++ public void connect(boolean bind) throws XMPPException { ++ // Establishes the connection, readers and writers ++ connectUsingConfiguration(config); ++ // Automatically makes the login if the user was previouslly connected successfully ++ // to the server and the connection was terminated abruptly ++ if (connected && wasAuthenticated) { ++ // Make the login ++ try { ++ if (isAnonymous()) { ++ // Make the anonymous login ++ loginAnonymously(); ++ } ++ else { ++ login(config.getUsername(), config.getPassword(), ++ bind ? config.getResource() : null); ++ } ++ packetReader.notifyReconnection(); ++ } ++ catch (XMPPException e) { ++ e.printStackTrace(); ++ } ++ } ++ } ++ ++ /** ++ * Sets whether the connection has already logged in the server. ++ * ++ * @param wasAuthenticated true if the connection has already been authenticated. ++ */ ++ private void setWasAuthenticated(boolean wasAuthenticated) { ++ if (!this.wasAuthenticated) { ++ this.wasAuthenticated = wasAuthenticated; ++ } ++ } ++ ++ @Override ++ public void setRosterStorage(RosterStorage storage) ++ throws IllegalStateException { ++ if(roster!=null){ ++ throw new IllegalStateException("Roster is already initialized"); ++ } ++ this.rosterStorage = storage; ++ } ++ ++ public boolean isEntityCapsEnabled() { ++ return config.isEntityCapsEnabled(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPException.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPException.java +new file mode 100644 +index 0000000..6da24c2 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPException.java +@@ -0,0 +1,219 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.packet.StreamError; ++import org.jivesoftware.smack.packet.XMPPError; ++ ++import java.io.PrintStream; ++import java.io.PrintWriter; ++ ++/** ++ * A generic exception that is thrown when an error occurs performing an ++ * XMPP operation. XMPP servers can respond to error conditions with an error code ++ * and textual description of the problem, which are encapsulated in the XMPPError ++ * class. When appropriate, an XMPPError instance is attached instances of this exception.

      ++ * ++ * When a stream error occured, the server will send a stream error to the client before ++ * closing the connection. Stream errors are unrecoverable errors. When a stream error ++ * is sent to the client an XMPPException will be thrown containing the StreamError sent ++ * by the server. ++ * ++ * @see XMPPError ++ * @author Matt Tucker ++ */ ++public class XMPPException extends Exception { ++ ++ private StreamError streamError = null; ++ private XMPPError error = null; ++ private Throwable wrappedThrowable = null; ++ ++ /** ++ * Creates a new XMPPException. ++ */ ++ public XMPPException() { ++ super(); ++ } ++ ++ /** ++ * Creates a new XMPPException with a description of the exception. ++ * ++ * @param message description of the exception. ++ */ ++ public XMPPException(String message) { ++ super(message); ++ } ++ ++ /** ++ * Creates a new XMPPException with the Throwable that was the root cause of the ++ * exception. ++ * ++ * @param wrappedThrowable the root cause of the exception. ++ */ ++ public XMPPException(Throwable wrappedThrowable) { ++ super(); ++ this.wrappedThrowable = wrappedThrowable; ++ } ++ ++ /** ++ * Cretaes a new XMPPException with the stream error that was the root case of the ++ * exception. When a stream error is received from the server then the underlying ++ * TCP connection will be closed by the server. ++ * ++ * @param streamError the root cause of the exception. ++ */ ++ public XMPPException(StreamError streamError) { ++ super(); ++ this.streamError = streamError; ++ } ++ ++ /** ++ * Cretaes a new XMPPException with the XMPPError that was the root case of the ++ * exception. ++ * ++ * @param error the root cause of the exception. ++ */ ++ public XMPPException(XMPPError error) { ++ super(); ++ this.error = error; ++ } ++ ++ /** ++ * Creates a new XMPPException with a description of the exception and the ++ * Throwable that was the root cause of the exception. ++ * ++ * @param message a description of the exception. ++ * @param wrappedThrowable the root cause of the exception. ++ */ ++ public XMPPException(String message, Throwable wrappedThrowable) { ++ super(message); ++ this.wrappedThrowable = wrappedThrowable; ++ } ++ ++ /** ++ * Creates a new XMPPException with a description of the exception, an XMPPError, ++ * and the Throwable that was the root cause of the exception. ++ * ++ * @param message a description of the exception. ++ * @param error the root cause of the exception. ++ * @param wrappedThrowable the root cause of the exception. ++ */ ++ public XMPPException(String message, XMPPError error, Throwable wrappedThrowable) { ++ super(message); ++ this.error = error; ++ this.wrappedThrowable = wrappedThrowable; ++ } ++ ++ /** ++ * Creates a new XMPPException with a description of the exception and the ++ * XMPPException that was the root cause of the exception. ++ * ++ * @param message a description of the exception. ++ * @param error the root cause of the exception. ++ */ ++ public XMPPException(String message, XMPPError error) { ++ super(message); ++ this.error = error; ++ } ++ ++ /** ++ * Returns the XMPPError asscociated with this exception, or null if there ++ * isn't one. ++ * ++ * @return the XMPPError asscociated with this exception. ++ */ ++ public XMPPError getXMPPError() { ++ return error; ++ } ++ ++ /** ++ * Returns the StreamError asscociated with this exception, or null if there ++ * isn't one. The underlying TCP connection is closed by the server after sending the ++ * stream error to the client. ++ * ++ * @return the StreamError asscociated with this exception. ++ */ ++ public StreamError getStreamError() { ++ return streamError; ++ } ++ ++ /** ++ * Returns the Throwable asscociated with this exception, or null if there ++ * isn't one. ++ * ++ * @return the Throwable asscociated with this exception. ++ */ ++ public Throwable getWrappedThrowable() { ++ return wrappedThrowable; ++ } ++ ++ public void printStackTrace() { ++ printStackTrace(System.err); ++ } ++ ++ public void printStackTrace(PrintStream out) { ++ super.printStackTrace(out); ++ if (wrappedThrowable != null) { ++ out.println("Nested Exception: "); ++ wrappedThrowable.printStackTrace(out); ++ } ++ } ++ ++ public void printStackTrace(PrintWriter out) { ++ super.printStackTrace(out); ++ if (wrappedThrowable != null) { ++ out.println("Nested Exception: "); ++ wrappedThrowable.printStackTrace(out); ++ } ++ } ++ ++ public String getMessage() { ++ String msg = super.getMessage(); ++ // If the message was not set, but there is an XMPPError, return the ++ // XMPPError as the message. ++ if (msg == null && error != null) { ++ return error.toString(); ++ } ++ else if (msg == null && streamError != null) { ++ return streamError.toString(); ++ } ++ return msg; ++ } ++ ++ public String toString() { ++ StringBuilder buf = new StringBuilder(); ++ String message = super.getMessage(); ++ if (message != null) { ++ buf.append(message).append(": "); ++ } ++ if (error != null) { ++ buf.append(error); ++ } ++ if (streamError != null) { ++ buf.append(streamError); ++ } ++ if (wrappedThrowable != null) { ++ buf.append("\n -- caused by: ").append(wrappedThrowable); ++ } ++ ++ return buf.toString(); ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPLLConnection.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPLLConnection.java +new file mode 100644 +index 0000000..04c7f19 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPLLConnection.java +@@ -0,0 +1,464 @@ ++/* ++ * Copyright 2009 Jonas Ã…dahl. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.debugger.SmackDebugger; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.XMPPError; ++ ++import java.io.*; ++import java.net.Socket; ++import java.net.UnknownHostException; ++import java.lang.reflect.Constructor; ++import java.util.Date; ++import java.util.Set; ++import java.util.concurrent.CopyOnWriteArraySet; ++ ++ ++/** ++ * Link-local XMPP connection according to XEP-0174 connection. Automatically ++ * created by LLService and closed by inactivity. ++ * ++ */ ++public class XMPPLLConnection extends XMPPConnection // public for debugging reasons ++{ ++ private final static Set linkLocalListeners = ++ new CopyOnWriteArraySet(); ++ ++ ++ private LLService service; ++ private LLPresence localPresence, remotePresence; ++ private boolean initiator; ++ private long lastActivity = 0; ++ protected XMPPLLConnection connection; ++ private Thread timeoutThread; ++ ++ /** ++ * Instantiate a new link-local connection. Use the config parameter to ++ * specify if the connection is acting as server or client. ++ * ++ * @param config specification about how the new connection is to be set up. ++ */ ++ XMPPLLConnection(LLService service, LLConnectionConfiguration config) { ++ super(config); ++ connection = this; ++ this.service = service; ++ updateLastActivity(); ++ ++ // A timeout thread's purpose is to close down inactive connections ++ // after a certain amount of seconds (defaults to 15). ++ timeoutThread = new Thread() { ++ public void run() { ++ try { ++ while (connection != null) { ++ //synchronized (connection) { ++ Thread.sleep(14000); ++ long currentTime = new Date().getTime(); ++ if (currentTime - lastActivity > 15000) { ++ shutdown(); ++ break; ++ } ++ //} ++ } ++ } catch (InterruptedException ie) { ++ shutdown(); ++ } ++ } ++ }; ++ ++ timeoutThread.setName("Smack Link-local Connection Timeout (" + connection.connectionCounterValue + ")"); ++ timeoutThread.setDaemon(true); ++ ++ ++ if (config.isInitiator()) { ++ // we are connecting to remote host ++ localPresence = config.getLocalPresence(); ++ remotePresence = config.getRemotePresence(); ++ config.setServiceName(remotePresence.getServiceName()); ++ initiator = true; ++ } else { ++ // a remote host connected to us ++ localPresence = config.getLocalPresence(); ++ remotePresence = null; ++ config.setServiceName(null); ++ initiator = false; ++ socket = config.getSocket(); ++ } ++ } ++ ++ /** ++ * Tells if this connection instance is the initiator. ++ * ++ * @return true if this instance is the one connecting to a remote peer. ++ */ ++ public boolean isInitiator() { ++ return initiator; ++ } ++ ++ /** ++ * Return the user name of the remote peer (service name). ++ * ++ * @return the remote hosts service name / username ++ */ ++ public String getUser() { ++ // username is the service name of the local presence ++ return localPresence.getServiceName(); ++ } ++ ++ /** ++ * Sets the name of the service provided in the from the remote peer. ++ * ++ * @param serviceName the name of the service ++ */ ++ public void setServiceName(String serviceName) { ++ config.setServiceName(serviceName); ++ } ++ ++ ++ /** ++ * Set the remote presence. Used when being connected, ++ * will not know the remote service name until stream is initiated. ++ * ++ * @param remotePresence presence information about the connecting client. ++ */ ++ void setRemotePresence(LLPresence remotePresence) { ++ this.remotePresence = remotePresence; ++ } ++ ++ /** ++ * Start listen for data and a stream tag. ++ */ ++ void initListen() throws XMPPException { ++ initConnection(); ++ } ++ ++ /** ++ * Adds a listener that are notified when a new link-local connection ++ * has been established. ++ * ++ * @param listener A class implementing the LLConnectionListener interface. ++ */ ++ public static void addLLConnectionListener(LLConnectionListener listener) { ++ linkLocalListeners.add(listener); ++ } ++ ++ /** ++ * Removes a listener from the new connection listener list. ++ * ++ * @param listener The class implementing the LLConnectionListener interface that ++ * is to be removed. ++ */ ++ public static void removeLLConnectionListener(LLConnectionListener listener) { ++ linkLocalListeners.remove(listener); ++ } ++ ++ /** ++ * Create a socket, connect to the remote peer and initiate a XMPP stream session. ++ */ ++ public void connect() throws XMPPException { ++ String host = remotePresence.getHost(); ++ int port = remotePresence.getPort(); ++ ++ try { ++ socket = new Socket(host, port); ++ } ++ catch (UnknownHostException uhe) { ++ String errorMessage = "Could not connect to " + host + ":" + port + "."; ++ throw new XMPPException(errorMessage, new XMPPError( ++ XMPPError.Condition.remote_server_timeout, errorMessage), ++ uhe); ++ } ++ catch (IOException ioe) { ++ String errorMessage = "Error connecting to " + host + ":" ++ + port + "."; ++ throw new XMPPException(errorMessage, new XMPPError( ++ XMPPError.Condition.remote_server_error, errorMessage), ioe); ++ } ++ initConnection(); ++ ++ notifyLLListenersConnected(); ++ } ++ ++ ++ /** ++ * Handles the opening of a stream after a remote client has connected and opened a stream. ++ * @throws XMPPException if service name is missing or service is unknown to the mDNS daemon. ++ */ ++ public void streamInitiatingReceived() throws XMPPException { ++ if (config.getServiceName() == null) { ++ shutdown(); ++ } else { ++ packetWriter = new PacketWriter(this); ++ if (debugger != null) { ++ if (debugger.getWriterListener() != null) { ++ addPacketListener(debugger.getWriterListener(), null); ++ } ++ } ++ packetWriter.startup(); ++ notifyLLListenersConnected(); ++ } ++ } ++ ++ /** ++ * Notify new connection listeners that a new connection has been established. ++ */ ++ private void notifyLLListenersConnected() { ++ for (LLConnectionListener listener : linkLocalListeners) { ++ listener.connectionCreated(this); ++ } ++ } ++ ++ /** ++ * Update the timer telling when the last activity happend. Used by timeout ++ * thread to tell how long the connection has been inactive. ++ */ ++ void updateLastActivity() { ++ lastActivity = new Date().getTime(); ++ } ++ ++ /** ++ * Sends the specified packet to the remote peer. ++ * ++ * @param packet the packet to send ++ */ ++ @Override ++ public void sendPacket(Packet packet) { ++ updateLastActivity(); ++ // always add the from='' attribute ++ packet.setFrom(getUser()); ++ super.sendPacket(packet); ++ } ++ ++ /** ++ * Initializes the connection by creating a packet reader and writer and opening a ++ * XMPP stream to the server. ++ * ++ * @throws XMPPException if establishing a connection to the server fails. ++ */ ++ private void initConnection() throws XMPPException { ++ // Set the reader and writer instance variables ++ initReaderAndWriter(); ++ timeoutThread.start(); ++ ++ try { ++ // Don't initialize packet writer until we know it's a valid connection ++ // unless we are the initiator. If we are NOT the initializer, we instead ++ // wait for a stream initiation before doing anything. ++ if (isInitiator()) ++ packetWriter = new PacketWriter(this); ++ ++ // Initialize packet reader ++ packetReader = new LLPacketReader(service, this); ++ ++ // If debugging is enabled, we should start the thread that will listen for ++ // all packets and then log them. ++ // XXX FIXME debugging enabled not working ++ if (false) {//configuration.isDebuggerEnabled()) { ++ addPacketListener(debugger.getReaderListener(), null); ++ } ++ ++ // Make note of the fact that we're now connected. ++ connected = true; ++ ++ // If we are the initiator start the packet writer. This will open a XMPP ++ // stream to the server. If not, a packet writer will be started after ++ // receiving an initial stream start tag. ++ if (isInitiator()) ++ packetWriter.startup(); ++ // Start the packet reader. The startup() method will block until we ++ // get an opening stream packet back from server. ++ packetReader.startup(); ++ } ++ catch (XMPPException ex) { ++ // An exception occurred in setting up the connection. Make sure we shut down the ++ // readers and writers and close the socket. ++ ++ if (packetWriter != null) { ++ try { ++ packetWriter.shutdown(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ packetWriter = null; ++ } ++ if (packetReader != null) { ++ try { ++ packetReader.shutdown(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ packetReader = null; ++ } ++ if (socket != null) { ++ try { ++ socket.close(); ++ } ++ catch (Exception e) { /* ignore */ } ++ socket = null; ++ } ++ // closing reader after socket since reader.close() blocks otherwise ++ if (reader != null) { ++ try { ++ reader.close(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ reader = null; ++ } ++ if (writer != null) { ++ try { ++ writer.close(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ writer = null; ++ } ++ connected = false; ++ ++ throw ex; // Everything stoppped. Now throw the exception. ++ } ++ } ++ ++ private void initReaderAndWriter() throws XMPPException { ++ try { ++ reader = ++ new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8")); ++ writer = new BufferedWriter( ++ new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); ++ } ++ catch (IOException ioe) { ++ throw new XMPPException( ++ "XMPPError establishing connection with server.", ++ new XMPPError(XMPPError.Condition.remote_server_error, ++ "XMPPError establishing connection with server."), ++ ioe); ++ } ++ ++ // If debugging is enabled, we open a window and write out all network traffic. ++ if (false) {//configuration.isDebuggerEnabled()) { ++ if (debugger == null) { ++ // Detect the debugger class to use. ++ String className = null; ++ // Use try block since we may not have permission to get a system ++ // property (for example, when an applet). ++ try { ++ className = System.getProperty("smack.debuggerClass"); ++ } ++ catch (Throwable t) { ++ // Ignore. ++ } ++ Class debuggerClass = null; ++ if (className != null) { ++ try { ++ debuggerClass = Class.forName(className); ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } ++ if (debuggerClass == null) { ++ try { ++ debuggerClass = ++ Class.forName("org.jivesoftware.smackx.debugger.EnhancedDebugger"); ++ } ++ catch (Exception ex) { ++ try { ++ debuggerClass = ++ Class.forName("org.jivesoftware.smack.debugger.LiteDebugger"); ++ } ++ catch (Exception ex2) { ++ ex2.printStackTrace(); ++ } ++ } ++ } ++ // Create a new debugger instance. If an exception occurs then disable the debugging ++ // option ++ try { ++ Constructor constructor = debuggerClass ++ .getConstructor(XMPPLLConnection.class, Writer.class, Reader.class); ++ debugger = (SmackDebugger) constructor.newInstance(this, writer, reader); ++ reader = debugger.getReader(); ++ writer = debugger.getWriter(); ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ DEBUG_ENABLED = false; ++ } ++ } ++ else { ++ // Obtain new reader and writer from the existing debugger ++ reader = debugger.newConnectionReader(reader); ++ writer = debugger.newConnectionWriter(writer); ++ } ++ } ++ } ++ ++ protected void shutdown() { ++ connection = null; ++ ++ if (packetReader != null) ++ packetReader.shutdown(); ++ if (packetWriter != null) ++ packetWriter.shutdown(); ++ ++ // Wait 150 ms for processes to clean-up, then shutdown. ++ try { ++ Thread.sleep(150); ++ } ++ catch (Exception e) { ++ // Ignore. ++ } ++ ++ // Close down the readers and writers. ++ if (reader != null) { ++ try { ++ reader.close(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ reader = null; ++ } ++ if (writer != null) { ++ try { ++ writer.close(); ++ } ++ catch (Throwable ignore) { /* ignore */ } ++ writer = null; ++ } ++ ++ try { ++ socket.close(); ++ } ++ catch (Exception e) { ++ // Ignore. ++ } ++ } ++ ++ public void disconnect() { ++ // If not connected, ignore this request. ++ if (packetReader == null || packetWriter == null) { ++ return; ++ } ++ ++ shutdown(); ++ ++ packetWriter.cleanup(); ++ packetWriter = null; ++ packetReader.cleanup(); ++ packetReader = null; ++ } ++ ++ @Override ++ public boolean isAddFrom() { ++ return true; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/debugger/ConsoleDebugger.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/debugger/ConsoleDebugger.java +new file mode 100644 +index 0000000..0d3e5b7 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/debugger/ConsoleDebugger.java +@@ -0,0 +1,181 @@ ++package org.jivesoftware.smack.debugger; ++ ++import org.jivesoftware.smack.ConnectionListener; ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.util.*; ++ ++import java.io.Reader; ++import java.io.Writer; ++import java.text.SimpleDateFormat; ++import java.util.Date; ++ ++/** ++ * Very simple debugger that prints to the console (stdout) the sent and received stanzas. Use ++ * this debugger with caution since printing to the console is an expensive operation that may ++ * even block the thread since only one thread may print at a time.

      ++ *

      ++ * It is possible to not only print the raw sent and received stanzas but also the interpreted ++ * packets by Smack. By default interpreted packets won't be printed. To enable this feature ++ * just change the printInterpreted static variable to true. ++ * ++ * @author Gaston Dombiak ++ */ ++public class ConsoleDebugger implements SmackDebugger { ++ ++ public static boolean printInterpreted = false; ++ private SimpleDateFormat dateFormatter = new SimpleDateFormat("hh:mm:ss aaa"); ++ ++ private Connection connection = null; ++ ++ private PacketListener listener = null; ++ private ConnectionListener connListener = null; ++ ++ private Writer writer; ++ private Reader reader; ++ private ReaderListener readerListener; ++ private WriterListener writerListener; ++ ++ public ConsoleDebugger(Connection connection, Writer writer, Reader reader) { ++ this.connection = connection; ++ this.writer = writer; ++ this.reader = reader; ++ createDebug(); ++ } ++ ++ /** ++ * Creates the listeners that will print in the console when new activity is detected. ++ */ ++ private void createDebug() { ++ // Create a special Reader that wraps the main Reader and logs data to the GUI. ++ ObservableReader debugReader = new ObservableReader(reader); ++ readerListener = new ReaderListener() { ++ public void read(String str) { ++ System.out.println( ++ dateFormatter.format(new Date()) + " RCV (" + connection.hashCode() + ++ "): " + ++ str); ++ } ++ }; ++ debugReader.addReaderListener(readerListener); ++ ++ // Create a special Writer that wraps the main Writer and logs data to the GUI. ++ ObservableWriter debugWriter = new ObservableWriter(writer); ++ writerListener = new WriterListener() { ++ public void write(String str) { ++ System.out.println( ++ dateFormatter.format(new Date()) + " SENT (" + connection.hashCode() + ++ "): " + ++ str); ++ } ++ }; ++ debugWriter.addWriterListener(writerListener); ++ ++ // Assign the reader/writer objects to use the debug versions. The packet reader ++ // and writer will use the debug versions when they are created. ++ reader = debugReader; ++ writer = debugWriter; ++ ++ // Create a thread that will listen for all incoming packets and write them to ++ // the GUI. This is what we call "interpreted" packet data, since it's the packet ++ // data as Smack sees it and not as it's coming in as raw XML. ++ listener = new PacketListener() { ++ public void processPacket(Packet packet) { ++ if (printInterpreted) { ++ System.out.println( ++ dateFormatter.format(new Date()) + " RCV PKT (" + ++ connection.hashCode() + ++ "): " + ++ packet.toXML()); ++ } ++ } ++ }; ++ ++ connListener = new ConnectionListener() { ++ public void connectionClosed() { ++ System.out.println( ++ dateFormatter.format(new Date()) + " Connection closed (" + ++ connection.hashCode() + ++ ")"); ++ } ++ ++ public void connectionClosedOnError(Exception e) { ++ System.out.println( ++ dateFormatter.format(new Date()) + ++ " Connection closed due to an exception (" + ++ connection.hashCode() + ++ ")"); ++ e.printStackTrace(); ++ } ++ public void reconnectionFailed(Exception e) { ++ System.out.println( ++ dateFormatter.format(new Date()) + ++ " Reconnection failed due to an exception (" + ++ connection.hashCode() + ++ ")"); ++ e.printStackTrace(); ++ } ++ public void reconnectionSuccessful() { ++ System.out.println( ++ dateFormatter.format(new Date()) + " Connection reconnected (" + ++ connection.hashCode() + ++ ")"); ++ } ++ public void reconnectingIn(int seconds) { ++ System.out.println( ++ dateFormatter.format(new Date()) + " Connection (" + ++ connection.hashCode() + ++ ") will reconnect in " + seconds); ++ } ++ }; ++ } ++ ++ public Reader newConnectionReader(Reader newReader) { ++ ((ObservableReader)reader).removeReaderListener(readerListener); ++ ObservableReader debugReader = new ObservableReader(newReader); ++ debugReader.addReaderListener(readerListener); ++ reader = debugReader; ++ return reader; ++ } ++ ++ public Writer newConnectionWriter(Writer newWriter) { ++ ((ObservableWriter)writer).removeWriterListener(writerListener); ++ ObservableWriter debugWriter = new ObservableWriter(newWriter); ++ debugWriter.addWriterListener(writerListener); ++ writer = debugWriter; ++ return writer; ++ } ++ ++ public void userHasLogged(String user) { ++ boolean isAnonymous = "".equals(StringUtils.parseName(user)); ++ String title = ++ "User logged (" + connection.hashCode() + "): " ++ + (isAnonymous ? "" : StringUtils.parseBareAddress(user)) ++ + "@" ++ + connection.getServiceName() ++ + ":" ++ + connection.getPort(); ++ title += "/" + StringUtils.parseResource(user); ++ System.out.println(title); ++ // Add the connection listener to the connection so that the debugger can be notified ++ // whenever the connection is closed. ++ connection.addConnectionListener(connListener); ++ } ++ ++ public Reader getReader() { ++ return reader; ++ } ++ ++ public Writer getWriter() { ++ return writer; ++ } ++ ++ public PacketListener getReaderListener() { ++ return listener; ++ } ++ ++ public PacketListener getWriterListener() { ++ return null; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/debugger/SmackDebugger.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/debugger/SmackDebugger.java +new file mode 100644 +index 0000000..562720b +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/debugger/SmackDebugger.java +@@ -0,0 +1,98 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.debugger; ++ ++import java.io.*; ++ ++import org.jivesoftware.smack.*; ++ ++/** ++ * Interface that allows for implementing classes to debug XML traffic. That is a GUI window that ++ * displays XML traffic.

      ++ * ++ * Every implementation of this interface must have a public constructor with the following ++ * arguments: Connection, Writer, Reader. ++ * ++ * @author Gaston Dombiak ++ */ ++public interface SmackDebugger { ++ ++ /** ++ * Called when a user has logged in to the server. The user could be an anonymous user, this ++ * means that the user would be of the form host/resource instead of the form ++ * user@host/resource. ++ * ++ * @param user the user@host/resource that has just logged in ++ */ ++ public abstract void userHasLogged(String user); ++ ++ /** ++ * Returns the special Reader that wraps the main Reader and logs data to the GUI. ++ * ++ * @return the special Reader that wraps the main Reader and logs data to the GUI. ++ */ ++ public abstract Reader getReader(); ++ ++ /** ++ * Returns the special Writer that wraps the main Writer and logs data to the GUI. ++ * ++ * @return the special Writer that wraps the main Writer and logs data to the GUI. ++ */ ++ public abstract Writer getWriter(); ++ ++ /** ++ * Returns a new special Reader that wraps the new connection Reader. The connection ++ * has been secured so the connection is using a new reader and writer. The debugger ++ * needs to wrap the new reader and writer to keep being notified of the connection ++ * traffic. ++ * ++ * @return a new special Reader that wraps the new connection Reader. ++ */ ++ public abstract Reader newConnectionReader(Reader reader); ++ ++ /** ++ * Returns a new special Writer that wraps the new connection Writer. The connection ++ * has been secured so the connection is using a new reader and writer. The debugger ++ * needs to wrap the new reader and writer to keep being notified of the connection ++ * traffic. ++ * ++ * @return a new special Writer that wraps the new connection Writer. ++ */ ++ public abstract Writer newConnectionWriter(Writer writer); ++ ++ /** ++ * Returns the thread that will listen for all incoming packets and write them to the GUI. ++ * This is what we call "interpreted" packet data, since it's the packet data as Smack sees ++ * it and not as it's coming in as raw XML. ++ * ++ * @return the PacketListener that will listen for all incoming packets and write them to ++ * the GUI ++ */ ++ public abstract PacketListener getReaderListener(); ++ ++ /** ++ * Returns the thread that will listen for all outgoing packets and write them to the GUI. ++ * ++ * @return the PacketListener that will listen for all sent packets and write them to ++ * the GUI ++ */ ++ public abstract PacketListener getWriterListener(); ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/debugger/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smack/debugger/package.html +new file mode 100644 +index 0000000..afb861f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/debugger/package.html +@@ -0,0 +1 @@ ++Core debugger functionality. +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/AndFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/AndFilter.java +new file mode 100644 +index 0000000..847b618 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/AndFilter.java +@@ -0,0 +1,91 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.filter; ++ ++import org.jivesoftware.smack.packet.Packet; ++ ++import java.util.List; ++import java.util.ArrayList; ++ ++/** ++ * Implements the logical AND operation over two or more packet filters. ++ * In other words, packets pass this filter if they pass all of the filters. ++ * ++ * @author Matt Tucker ++ */ ++public class AndFilter implements PacketFilter { ++ ++ /** ++ * The list of filters. ++ */ ++ private List filters = new ArrayList(); ++ ++ /** ++ * Creates an empty AND filter. Filters should be added using the ++ * {@link #addFilter(PacketFilter)} method. ++ */ ++ public AndFilter() { ++ ++ } ++ ++ /** ++ * Creates an AND filter using the specified filters. ++ * ++ * @param filters the filters to add. ++ */ ++ public AndFilter(PacketFilter... filters) { ++ if (filters == null) { ++ throw new IllegalArgumentException("Parameter cannot be null."); ++ } ++ for(PacketFilter filter : filters) { ++ if(filter == null) { ++ throw new IllegalArgumentException("Parameter cannot be null."); ++ } ++ this.filters.add(filter); ++ } ++ } ++ ++ /** ++ * Adds a filter to the filter list for the AND operation. A packet ++ * will pass the filter if all of the filters in the list accept it. ++ * ++ * @param filter a filter to add to the filter list. ++ */ ++ public void addFilter(PacketFilter filter) { ++ if (filter == null) { ++ throw new IllegalArgumentException("Parameter cannot be null."); ++ } ++ filters.add(filter); ++ } ++ ++ public boolean accept(Packet packet) { ++ for (PacketFilter filter : filters) { ++ if (!filter.accept(packet)) { ++ return false; ++ } ++ } ++ return true; ++ } ++ ++ public String toString() { ++ return filters.toString(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/FromContainsFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/FromContainsFilter.java +new file mode 100644 +index 0000000..bc372f9 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/FromContainsFilter.java +@@ -0,0 +1,54 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.filter; ++ ++import org.jivesoftware.smack.packet.Packet; ++ ++/** ++ * Filters for packets where the "from" field contains a specified value. ++ * ++ * @author Matt Tucker ++ */ ++public class FromContainsFilter implements PacketFilter { ++ ++ private String from; ++ ++ /** ++ * Creates a "from" contains filter using the "from" field part. ++ * ++ * @param from the from field value the packet must contain. ++ */ ++ public FromContainsFilter(String from) { ++ if (from == null) { ++ throw new IllegalArgumentException("Parameter cannot be null."); ++ } ++ this.from = from.toLowerCase(); ++ } ++ ++ public boolean accept(Packet packet) { ++ if (packet.getFrom() == null) { ++ return false; ++ } ++ else { ++ return packet.getFrom().toLowerCase().indexOf(from) != -1; ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/FromMatchesFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/FromMatchesFilter.java +new file mode 100644 +index 0000000..e1dfa6c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/FromMatchesFilter.java +@@ -0,0 +1,75 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.filter; ++ ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.util.StringUtils; ++ ++/** ++ * Filter for packets where the "from" field exactly matches a specified JID. If the specified ++ * address is a bare JID then the filter will match any address whose bare JID matches the ++ * specified JID. But if the specified address is a full JID then the filter will only match ++ * if the sender of the packet matches the specified resource. ++ * ++ * @author Gaston Dombiak ++ */ ++public class FromMatchesFilter implements PacketFilter { ++ ++ private String address; ++ /** ++ * Flag that indicates if the checking will be done against bare JID addresses or full JIDs. ++ */ ++ private boolean matchBareJID = false; ++ ++ /** ++ * Creates a "from" filter using the "from" field part. If the specified address is a bare JID ++ * then the filter will match any address whose bare JID matches the specified JID. But if the ++ * specified address is a full JID then the filter will only match if the sender of the packet ++ * matches the specified resource. ++ * ++ * @param address the from field value the packet must match. Could be a full or bare JID. ++ */ ++ public FromMatchesFilter(String address) { ++ if (address == null) { ++ throw new IllegalArgumentException("Parameter cannot be null."); ++ } ++ this.address = address.toLowerCase(); ++ matchBareJID = "".equals(StringUtils.parseResource(address)); ++ } ++ ++ public boolean accept(Packet packet) { ++ if (packet.getFrom() == null) { ++ return false; ++ } ++ else if (matchBareJID) { ++ // Check if the bare JID of the sender of the packet matches the specified JID ++ return packet.getFrom().toLowerCase().startsWith(address); ++ } ++ else { ++ // Check if the full JID of the sender of the packet matches the specified JID ++ return address.equals(packet.getFrom().toLowerCase()); ++ } ++ } ++ ++ public String toString() { ++ return "FromMatchesFilter: " + address; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/IQTypeFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/IQTypeFilter.java +new file mode 100644 +index 0000000..6f080c5 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/IQTypeFilter.java +@@ -0,0 +1,48 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.filter; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++ ++/** ++ * A filter for IQ packet types. Returns true only if the packet is an IQ packet ++ * and it matches the type provided in the constructor. ++ * ++ * @author Alexander Wenckus ++ * ++ */ ++public class IQTypeFilter implements PacketFilter { ++ ++ private IQ.Type type; ++ ++ public IQTypeFilter(IQ.Type type) { ++ this.type = type; ++ } ++ ++ /* ++ * (non-Javadoc) ++ * ++ * @see org.jivesoftware.smack.filter.PacketFilter#accept(org.jivesoftware.smack.packet.Packet) ++ */ ++ public boolean accept(Packet packet) { ++ return (packet instanceof IQ && ((IQ) packet).getType().equals(type)); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/MessageTypeFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/MessageTypeFilter.java +new file mode 100644 +index 0000000..a3430ec +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/MessageTypeFilter.java +@@ -0,0 +1,54 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.filter; ++ ++import org.jivesoftware.smack.packet.Message; ++import org.jivesoftware.smack.packet.Packet; ++ ++/** ++ * Filters for packets of a specific type of Message (e.g. CHAT). ++ * ++ * @see org.jivesoftware.smack.packet.Message.Type ++ * @author Ward Harold ++ */ ++public class MessageTypeFilter implements PacketFilter { ++ ++ private final Message.Type type; ++ ++ /** ++ * Creates a new message type filter using the specified message type. ++ * ++ * @param type the message type. ++ */ ++ public MessageTypeFilter(Message.Type type) { ++ this.type = type; ++ } ++ ++ public boolean accept(Packet packet) { ++ if (!(packet instanceof Message)) { ++ return false; ++ } ++ else { ++ return ((Message) packet).getType().equals(this.type); ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/NotFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/NotFilter.java +new file mode 100644 +index 0000000..59537d0 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/NotFilter.java +@@ -0,0 +1,50 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.filter; ++ ++import org.jivesoftware.smack.packet.Packet; ++ ++/** ++ * Implements the logical NOT operation on a packet filter. In other words, packets ++ * pass this filter if they do not pass the supplied filter. ++ * ++ * @author Matt Tucker ++ */ ++public class NotFilter implements PacketFilter { ++ ++ private PacketFilter filter; ++ ++ /** ++ * Creates a NOT filter using the specified filter. ++ * ++ * @param filter the filter. ++ */ ++ public NotFilter(PacketFilter filter) { ++ if (filter == null) { ++ throw new IllegalArgumentException("Parameter cannot be null."); ++ } ++ this.filter = filter; ++ } ++ ++ public boolean accept(Packet packet) { ++ return !filter.accept(packet); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/OrFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/OrFilter.java +new file mode 100644 +index 0000000..4c34fd0 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/OrFilter.java +@@ -0,0 +1,103 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.filter; ++ ++import org.jivesoftware.smack.packet.Packet; ++ ++/** ++ * Implements the logical OR operation over two or more packet filters. In ++ * other words, packets pass this filter if they pass any of the filters. ++ * ++ * @author Matt Tucker ++ */ ++public class OrFilter implements PacketFilter { ++ ++ /** ++ * The current number of elements in the filter. ++ */ ++ private int size; ++ ++ /** ++ * The list of filters. ++ */ ++ private PacketFilter [] filters; ++ ++ /** ++ * Creates an empty OR filter. Filters should be added using the ++ * {@link #addFilter(PacketFilter)} method. ++ */ ++ public OrFilter() { ++ size = 0; ++ filters = new PacketFilter[3]; ++ } ++ ++ /** ++ * Creates an OR filter using the two specified filters. ++ * ++ * @param filter1 the first packet filter. ++ * @param filter2 the second packet filter. ++ */ ++ public OrFilter(PacketFilter filter1, PacketFilter filter2) { ++ if (filter1 == null || filter2 == null) { ++ throw new IllegalArgumentException("Parameters cannot be null."); ++ } ++ size = 2; ++ filters = new PacketFilter[2]; ++ filters[0] = filter1; ++ filters[1] = filter2; ++ } ++ ++ /** ++ * Adds a filter to the filter list for the OR operation. A packet ++ * will pass the filter if any filter in the list accepts it. ++ * ++ * @param filter a filter to add to the filter list. ++ */ ++ public void addFilter(PacketFilter filter) { ++ if (filter == null) { ++ throw new IllegalArgumentException("Parameter cannot be null."); ++ } ++ // If there is no more room left in the filters array, expand it. ++ if (size == filters.length) { ++ PacketFilter [] newFilters = new PacketFilter[filters.length+2]; ++ for (int i=0; i ++ * ++ * Several pre-defined filters are defined. These filters can be logically combined ++ * for more complex packet filtering by using the ++ * {@link org.jivesoftware.smack.filter.AndFilter AndFilter} and ++ * {@link org.jivesoftware.smack.filter.OrFilter OrFilter} filters. It's also possible ++ * to define your own filters by implementing this interface. The code example below ++ * creates a trivial filter for packets with a specific ID. ++ * ++ *

      ++ * // Use an anonymous inner class to define a packet filter that returns
      ++ * // all packets that have a packet ID of "RS145".
      ++ * PacketFilter myFilter = new PacketFilter() {
      ++ *     public boolean accept(Packet packet) {
      ++ *         return "RS145".equals(packet.getPacketID());
      ++ *     }
      ++ * };
      ++ * // Create a new packet collector using the filter we created.
      ++ * PacketCollector myCollector = packetReader.createPacketCollector(myFilter);
      ++ * 
      ++ * ++ * @see org.jivesoftware.smack.PacketCollector ++ * @see org.jivesoftware.smack.PacketListener ++ * @author Matt Tucker ++ */ ++public interface PacketFilter { ++ ++ /** ++ * Tests whether or not the specified packet should pass the filter. ++ * ++ * @param packet the packet to test. ++ * @return true if and only if packet passes the filter. ++ */ ++ public boolean accept(Packet packet); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/PacketIDFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/PacketIDFilter.java +new file mode 100644 +index 0000000..8d68201 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/PacketIDFilter.java +@@ -0,0 +1,53 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.filter; ++ ++import org.jivesoftware.smack.packet.Packet; ++ ++/** ++ * Filters for packets with a particular packet ID. ++ * ++ * @author Matt Tucker ++ */ ++public class PacketIDFilter implements PacketFilter { ++ ++ private String packetID; ++ ++ /** ++ * Creates a new packet ID filter using the specified packet ID. ++ * ++ * @param packetID the packet ID to filter for. ++ */ ++ public PacketIDFilter(String packetID) { ++ if (packetID == null) { ++ throw new IllegalArgumentException("Packet ID cannot be null."); ++ } ++ this.packetID = packetID; ++ } ++ ++ public boolean accept(Packet packet) { ++ return packetID.equals(packet.getPacketID()); ++ } ++ ++ public String toString() { ++ return "PacketIDFilter by id: " + packetID; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/PacketTypeFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/PacketTypeFilter.java +new file mode 100644 +index 0000000..24a8539 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/PacketTypeFilter.java +@@ -0,0 +1,61 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.filter; ++ ++import org.jivesoftware.smack.packet.Packet; ++ ++/** ++ * Filters for packets of a particular type. The type is given as a Class object, so ++ * example types would: ++ *
        ++ *
      • Message.class ++ *
      • IQ.class ++ *
      • Presence.class ++ *
      ++ * ++ * @author Matt Tucker ++ */ ++public class PacketTypeFilter implements PacketFilter { ++ ++ Class packetType; ++ ++ /** ++ * Creates a new packet type filter that will filter for packets that are the ++ * same type as packetType. ++ * ++ * @param packetType the Class type. ++ */ ++ public PacketTypeFilter(Class packetType) { ++ // Ensure the packet type is a sub-class of Packet. ++ if (!Packet.class.isAssignableFrom(packetType)) { ++ throw new IllegalArgumentException("Packet type must be a sub-class of Packet."); ++ } ++ this.packetType = packetType; ++ } ++ ++ public boolean accept(Packet packet) { ++ return packetType.isInstance(packet); ++ } ++ ++ public String toString() { ++ return "PacketTypeFilter: " + packetType.getName(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/ThreadFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/ThreadFilter.java +new file mode 100644 +index 0000000..8ba8b2e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/ThreadFilter.java +@@ -0,0 +1,50 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.filter; ++ ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.Message; ++ ++/** ++ * Filters for message packets with a particular thread value. ++ * ++ * @author Matt Tucker ++ */ ++public class ThreadFilter implements PacketFilter { ++ ++ private String thread; ++ ++ /** ++ * Creates a new thread filter using the specified thread value. ++ * ++ * @param thread the thread value to filter for. ++ */ ++ public ThreadFilter(String thread) { ++ if (thread == null) { ++ throw new IllegalArgumentException("Thread cannot be null."); ++ } ++ this.thread = thread; ++ } ++ ++ public boolean accept(Packet packet) { ++ return packet instanceof Message && thread.equals(((Message) packet).getThread()); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/ToContainsFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/ToContainsFilter.java +new file mode 100644 +index 0000000..8069fcc +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/ToContainsFilter.java +@@ -0,0 +1,55 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.filter; ++ ++import org.jivesoftware.smack.packet.Packet; ++ ++/** ++ * Filters for packets where the "to" field contains a specified value. For example, ++ * the filter could be used to listen for all packets sent to a group chat nickname. ++ * ++ * @author Matt Tucker ++ */ ++public class ToContainsFilter implements PacketFilter { ++ ++ private String to; ++ ++ /** ++ * Creates a "to" contains filter using the "to" field part. ++ * ++ * @param to the to field value the packet must contain. ++ */ ++ public ToContainsFilter(String to) { ++ if (to == null) { ++ throw new IllegalArgumentException("Parameter cannot be null."); ++ } ++ this.to = to.toLowerCase(); ++ } ++ ++ public boolean accept(Packet packet) { ++ if (packet.getTo() == null) { ++ return false; ++ } ++ else { ++ return packet.getTo().toLowerCase().indexOf(to) != -1; ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/package.html +new file mode 100644 +index 0000000..8b3fe80 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/package.html +@@ -0,0 +1 @@ ++Allows {@link org.jivesoftware.smack.PacketCollector} and {@link org.jivesoftware.smack.PacketListener} instances to filter for packets with particular attributes. +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smack/package.html +new file mode 100644 +index 0000000..2758d78 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/package.html +@@ -0,0 +1 @@ ++Core classes of the Smack API. +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Authentication.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Authentication.java +new file mode 100644 +index 0000000..a47c079 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Authentication.java +@@ -0,0 +1,186 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.util.StringUtils; ++ ++/** ++ * Authentication packet, which can be used to login to a XMPP server as well ++ * as discover login information from the server. ++ */ ++public class Authentication extends IQ { ++ ++ private String username = null; ++ private String password = null; ++ private String digest = null; ++ private String resource = null; ++ ++ /** ++ * Create a new authentication packet. By default, the packet will be in ++ * "set" mode in order to perform an actual authentication with the server. ++ * In order to send a "get" request to get the available authentication ++ * modes back from the server, change the type of the IQ packet to "get": ++ *

      ++ *

      setType(IQ.Type.GET); ++ */ ++ public Authentication() { ++ setType(IQ.Type.SET); ++ } ++ ++ /** ++ * Returns the username, or null if the username hasn't been sent. ++ * ++ * @return the username. ++ */ ++ public String getUsername() { ++ return username; ++ } ++ ++ /** ++ * Sets the username. ++ * ++ * @param username the username. ++ */ ++ public void setUsername(String username) { ++ this.username = username; ++ } ++ ++ /** ++ * Returns the plain text password or null if the password hasn't ++ * been set. ++ * ++ * @return the password. ++ */ ++ public String getPassword() { ++ return password; ++ } ++ ++ /** ++ * Sets the plain text password. ++ * ++ * @param password the password. ++ */ ++ public void setPassword(String password) { ++ this.password = password; ++ } ++ ++ /** ++ * Returns the password digest or null if the digest hasn't ++ * been set. Password digests offer a more secure alternative for ++ * authentication compared to plain text. The digest is the hex-encoded ++ * SHA-1 hash of the connection ID plus the user's password. If the ++ * digest and password are set, digest authentication will be used. If ++ * only one value is set, the respective authentication mode will be used. ++ * ++ * @return the digest of the user's password. ++ */ ++ public String getDigest() { ++ return digest; ++ } ++ ++ /** ++ * Sets the digest value using a connection ID and password. Password ++ * digests offer a more secure alternative for authentication compared to ++ * plain text. The digest is the hex-encoded SHA-1 hash of the connection ID ++ * plus the user's password. If the digest and password are set, digest ++ * authentication will be used. If only one value is set, the respective ++ * authentication mode will be used. ++ * ++ * @param connectionID the connection ID. ++ * @param password the password. ++ * @see org.jivesoftware.smack.Connection#getConnectionID() ++ */ ++ public void setDigest(String connectionID, String password) { ++ this.digest = StringUtils.hash(connectionID + password); ++ } ++ ++ /** ++ * Sets the digest value directly. Password digests offer a more secure ++ * alternative for authentication compared to plain text. The digest is ++ * the hex-encoded SHA-1 hash of the connection ID plus the user's password. ++ * If the digest and password are set, digest authentication will be used. ++ * If only one value is set, the respective authentication mode will be used. ++ * ++ * @param digest the digest, which is the SHA-1 hash of the connection ID ++ * the user's password, encoded as hex. ++ * @see org.jivesoftware.smack.Connection#getConnectionID() ++ */ ++ public void setDigest(String digest) { ++ this.digest = digest; ++ } ++ ++ /** ++ * Returns the resource or null if the resource hasn't been set. ++ * ++ * @return the resource. ++ */ ++ public String getResource() { ++ return resource; ++ } ++ ++ /** ++ * Sets the resource. ++ * ++ * @param resource the resource. ++ */ ++ public void setResource(String resource) { ++ this.resource = resource; ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ if (username != null) { ++ if (username.equals("")) { ++ buf.append(""); ++ } ++ else { ++ buf.append("").append(username).append(""); ++ } ++ } ++ if (digest != null) { ++ if (digest.equals("")) { ++ buf.append(""); ++ } ++ else { ++ buf.append("").append(digest).append(""); ++ } ++ } ++ if (password != null && digest == null) { ++ if (password.equals("")) { ++ buf.append(""); ++ } ++ else { ++ buf.append("").append(StringUtils.escapeForXML(password)).append(""); ++ } ++ } ++ if (resource != null) { ++ if (resource.equals("")) { ++ buf.append(""); ++ } ++ else { ++ buf.append("").append(resource).append(""); ++ } ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Bind.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Bind.java +new file mode 100644 +index 0000000..e0c4b5c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Bind.java +@@ -0,0 +1,71 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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; ++ ++/** ++ * IQ packet used by Smack to bind a resource and to obtain the jid assigned by the server. ++ * There are two ways to bind a resource. One is simply sending an empty Bind packet where the ++ * server will assign a new resource for this connection. The other option is to set a desired ++ * resource but the server may return a modified version of the sent resource.

      ++ * ++ * For more information refer to the following ++ * link. ++ * ++ * @author Gaston Dombiak ++ */ ++public class Bind extends IQ { ++ ++ private String resource = null; ++ private String jid = null; ++ ++ public Bind() { ++ setType(IQ.Type.SET); ++ } ++ ++ public String getResource() { ++ return resource; ++ } ++ ++ public void setResource(String resource) { ++ this.resource = resource; ++ } ++ ++ public String getJid() { ++ return jid; ++ } ++ ++ public void setJid(String jid) { ++ this.jid = jid; ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ if (resource != null) { ++ buf.append("").append(resource).append(""); ++ } ++ if (jid != null) { ++ buf.append("").append(jid).append(""); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/DefaultPacketExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/DefaultPacketExtension.java +new file mode 100644 +index 0000000..6cc7934 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/DefaultPacketExtension.java +@@ -0,0 +1,133 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.*; ++ ++/** ++ * Default implementation of the PacketExtension interface. Unless a PacketExtensionProvider ++ * is registered with {@link org.jivesoftware.smack.provider.ProviderManager ProviderManager}, ++ * instances of this class will be returned when getting packet extensions.

      ++ * ++ * This class provides a very simple representation of an XML sub-document. Each element ++ * is a key in a Map with its CDATA being the value. For example, given the following ++ * XML sub-document: ++ * ++ *

      ++ * <foo xmlns="http://bar.com">
      ++ *     <color>blue</color>
      ++ *     <food>pizza</food>
      ++ * </foo>
      ++ * ++ * In this case, getValue("color") would return "blue", and getValue("food") would ++ * return "pizza". This parsing mechanism mechanism is very simplistic and will not work ++ * as desired in all cases (for example, if some of the elements have attributes. In those ++ * cases, a custom PacketExtensionProvider should be used. ++ * ++ * @author Matt Tucker ++ */ ++public class DefaultPacketExtension implements PacketExtension { ++ ++ private String elementName; ++ private String namespace; ++ private Map map; ++ ++ /** ++ * Creates a new generic packet extension. ++ * ++ * @param elementName the name of the element of the XML sub-document. ++ * @param namespace the namespace of the element. ++ */ ++ public DefaultPacketExtension(String elementName, String namespace) { ++ this.elementName = elementName; ++ this.namespace = namespace; ++ } ++ ++ /** ++ * Returns the XML element name of the extension sub-packet root element. ++ * ++ * @return the XML element name of the packet extension. ++ */ ++ public String getElementName() { ++ return elementName; ++ } ++ ++ /** ++ * Returns the XML namespace of the extension sub-packet root element. ++ * ++ * @return the XML namespace of the packet extension. ++ */ ++ public String getNamespace() { ++ return namespace; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(elementName).append(" xmlns=\"").append(namespace).append("\">"); ++ for (String name : getNames()) { ++ String value = getValue(name); ++ buf.append("<").append(name).append(">"); ++ buf.append(value); ++ buf.append(""); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ /** ++ * Returns an unmodifiable collection of the names that can be used to get ++ * values of the packet extension. ++ * ++ * @return the names. ++ */ ++ public synchronized Collection getNames() { ++ if (map == null) { ++ return Collections.emptySet(); ++ } ++ return Collections.unmodifiableSet(new HashMap(map).keySet()); ++ } ++ ++ /** ++ * Returns a packet extension value given a name. ++ * ++ * @param name the name. ++ * @return the value. ++ */ ++ public synchronized String getValue(String name) { ++ if (map == null) { ++ return null; ++ } ++ return map.get(name); ++ } ++ ++ /** ++ * Sets a packet extension value using the given name. ++ * ++ * @param name the name. ++ * @param value the value. ++ */ ++ public synchronized void setValue(String name, String value) { ++ if (map == null) { ++ map = new HashMap(); ++ } ++ map.put(name, value); ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/IQ.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/IQ.java +new file mode 100644 +index 0000000..8b84467 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/IQ.java +@@ -0,0 +1,236 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.util.StringUtils; ++ ++/** ++ * The base IQ (Info/Query) packet. IQ packets are used to get and set information ++ * on the server, including authentication, roster operations, and creating ++ * accounts. Each IQ packet has a specific type that indicates what type of action ++ * is being taken: "get", "set", "result", or "error".

      ++ * ++ * IQ packets can contain a single child element that exists in a specific XML ++ * namespace. The combination of the element name and namespace determines what ++ * type of IQ packet it is. Some example IQ subpacket snippets:

        ++ * ++ *
      • <query xmlns="jabber:iq:auth"> -- an authentication IQ. ++ *
      • <query xmlns="jabber:iq:private"> -- a private storage IQ. ++ *
      • <pubsub xmlns="http://jabber.org/protocol/pubsub"> -- a pubsub IQ. ++ *
      ++ * ++ * @author Matt Tucker ++ */ ++public abstract class IQ extends Packet { ++ ++ private Type type = Type.GET; ++ ++ /** ++ * Returns the type of the IQ packet. ++ * ++ * @return the type of the IQ packet. ++ */ ++ public Type getType() { ++ return type; ++ } ++ ++ /** ++ * Sets the type of the IQ packet. ++ * ++ * @param type the type of the IQ packet. ++ */ ++ public void setType(Type type) { ++ if (type == null) { ++ this.type = Type.GET; ++ } ++ else { ++ this.type = type; ++ } ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ } ++ else { ++ buf.append("type=\"").append(getType()).append("\">"); ++ } ++ // Add the query section if there is one. ++ String queryXML = getChildElementXML(); ++ if (queryXML != null) { ++ buf.append(queryXML); ++ } ++ // Add the error sub-packet, if there is one. ++ XMPPError error = getError(); ++ if (error != null) { ++ buf.append(error.toXML()); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ /** ++ * Returns the sub-element XML section of the IQ packet, or null if there ++ * isn't one. Packet extensions must be included, if any are defined.

      ++ * ++ * Extensions of this class must override this method. ++ * ++ * @return the child element section of the IQ XML. ++ */ ++ public abstract String getChildElementXML(); ++ ++ /** ++ * 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} ++ * IQ. The new packet will be initialized with:

        ++ *
      • The sender set to the recipient of the originating IQ. ++ *
      • The recipient set to the sender of the originating IQ. ++ *
      • The type set to {@link Type#RESULT IQ.Type.RESULT}. ++ *
      • The id set to the id of the originating IQ. ++ *
      • No child element of the IQ element. ++ *
      ++ * ++ * @param iq the {@link Type#GET IQ.Type.GET} or {@link Type#SET IQ.Type.SET} IQ packet. ++ * @throws IllegalArgumentException if the IQ packet does not have a type of ++ * {@link Type#GET IQ.Type.GET} or {@link Type#SET IQ.Type.SET}. ++ * @return a new {@link Type#RESULT IQ.Type.RESULT} IQ based on the originating IQ. ++ */ ++ public static IQ createResultIQ(final IQ 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()); ++ } ++ final IQ result = new IQ() { ++ public String getChildElementXML() { ++ return null; ++ } ++ }; ++ result.setType(Type.RESULT); ++ result.setPacketID(request.getPacketID()); ++ result.setFrom(request.getTo()); ++ result.setTo(request.getFrom()); ++ return result; ++ } ++ ++ /** ++ * 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 packet will be initialized with:
        ++ *
      • The sender set to the recipient of the originating IQ. ++ *
      • The recipient set to the sender of the originating IQ. ++ *
      • The type set to {@link Type#ERROR IQ.Type.ERROR}. ++ *
      • The id set to the id of the originating IQ. ++ *
      • The child element contained in the associated originating IQ. ++ *
      • The provided {@link XMPPError XMPPError}. ++ *
      ++ * ++ * @param iq 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 packet 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 IQ createErrorResponse(final IQ request, final XMPPError error) { ++ if (!(request.getType() == Type.GET || request.getType() == Type.SET)) { ++ throw new IllegalArgumentException( ++ "IQ must be of type 'set' or 'get'. Original IQ: " + request.toXML()); ++ } ++ final IQ result = new IQ() { ++ public String getChildElementXML() { ++ return request.getChildElementXML(); ++ } ++ }; ++ result.setType(Type.ERROR); ++ result.setPacketID(request.getPacketID()); ++ result.setFrom(request.getTo()); ++ result.setTo(request.getFrom()); ++ result.setError(error); ++ return result; ++ } ++ ++ /** ++ * A class to represent the type of the IQ packet. The types are: ++ * ++ *
        ++ *
      • IQ.Type.GET ++ *
      • IQ.Type.SET ++ *
      • IQ.Type.RESULT ++ *
      • IQ.Type.ERROR ++ *
      ++ */ ++ public static class Type { ++ ++ public static final Type GET = new Type("get"); ++ public static final Type SET = new Type("set"); ++ public static final Type RESULT = new Type("result"); ++ public static final Type ERROR = new Type("error"); ++ ++ /** ++ * Converts a String into the corresponding types. Valid String values ++ * that can be converted to types are: "get", "set", "result", and "error". ++ * ++ * @param type the String value to covert. ++ * @return the corresponding Type. ++ */ ++ public static Type fromString(String type) { ++ if (type == null) { ++ return null; ++ } ++ type = type.toLowerCase(); ++ if (GET.toString().equals(type)) { ++ return GET; ++ } ++ else if (SET.toString().equals(type)) { ++ return SET; ++ } ++ else if (ERROR.toString().equals(type)) { ++ return ERROR; ++ } ++ else if (RESULT.toString().equals(type)) { ++ return RESULT; ++ } ++ else { ++ return null; ++ } ++ } ++ ++ private String value; ++ ++ private Type(String value) { ++ this.value = value; ++ } ++ ++ public String toString() { ++ return value; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Message.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Message.java +new file mode 100644 +index 0000000..d28a9f4 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Message.java +@@ -0,0 +1,672 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.util.StringUtils; ++ ++import java.util.*; ++ ++/** ++ * Represents XMPP message packets. A message can be one of several types: ++ * ++ *
        ++ *
      • Message.Type.NORMAL -- (Default) a normal text message used in email like interface. ++ *
      • Message.Type.CHAT -- a typically short text message used in line-by-line chat interfaces. ++ *
      • Message.Type.GROUP_CHAT -- a chat message sent to a groupchat server for group chats. ++ *
      • Message.Type.HEADLINE -- a text message to be displayed in scrolling marquee displays. ++ *
      • Message.Type.ERROR -- indicates a messaging error. ++ *
      ++ * ++ * For each message type, different message fields are typically used as follows: ++ *

      ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ *
       Message type
      FieldNormalChatGroup ChatHeadlineXMPPError
      subject SHOULDSHOULD NOTSHOULD NOTSHOULD NOTSHOULD NOT
      thread OPTIONALSHOULDOPTIONALOPTIONALSHOULD NOT
      body SHOULDSHOULDSHOULDSHOULDSHOULD NOT
      error MUST NOTMUST NOTMUST NOTMUST NOTMUST
      ++ * ++ * @author Matt Tucker ++ */ ++public class Message extends Packet { ++ ++ private Type type = Type.normal; ++ private String thread = null; ++ private String language; ++ ++ private final Set subjects = new HashSet(); ++ private final Set bodies = new HashSet(); ++ ++ /** ++ * Creates a new, "normal" message. ++ */ ++ public Message() { ++ } ++ ++ /** ++ * Creates a new "normal" message to the specified recipient. ++ * ++ * @param to the recipient of the message. ++ */ ++ public Message(String to) { ++ setTo(to); ++ } ++ ++ /** ++ * Creates a new message of the specified type to a recipient. ++ * ++ * @param to the user to send the message to. ++ * @param type the message type. ++ */ ++ public Message(String to, Type type) { ++ setTo(to); ++ this.type = type; ++ } ++ ++ /** ++ * 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. ++ */ ++ public Type getType() { ++ return type; ++ } ++ ++ /** ++ * Sets the type of the message. ++ * ++ * @param type the type of the message. ++ * @throws IllegalArgumentException if null is passed in as the type ++ */ ++ public void setType(Type type) { ++ if (type == null) { ++ throw new IllegalArgumentException("Type cannot be null."); ++ } ++ this.type = type; ++ } ++ ++ /** ++ * Returns the default subject of the message, or null if the subject has not been set. ++ * The subject is a short description of message contents. ++ *

      ++ * The default subject of a message is the subject that corresponds to the message's language. ++ * (see {@link #getLanguage()}) or if no language is set to the applications default ++ * language (see {@link Packet#getDefaultLanguage()}). ++ * ++ * @return the subject of the message. ++ */ ++ public String getSubject() { ++ return getSubject(null); ++ } ++ ++ /** ++ * Returns the subject corresponding to the language. If the language is null, the method result ++ * will be the same as {@link #getSubject()}. Null will be returned if the language does not have ++ * a corresponding subject. ++ * ++ * @param language the language of the subject to return. ++ * @return the subject related to the passed in language. ++ */ ++ public String getSubject(String language) { ++ Subject subject = getMessageSubject(language); ++ return subject == null ? null : subject.subject; ++ } ++ ++ private Subject getMessageSubject(String language) { ++ language = determineLanguage(language); ++ for (Subject subject : subjects) { ++ if (language.equals(subject.language)) { ++ return subject; ++ } ++ } ++ return null; ++ } ++ ++ /** ++ * Returns a set of all subjects in this Message, including the default message subject accessible ++ * from {@link #getSubject()}. ++ * ++ * @return a collection of all subjects in this message. ++ */ ++ public Collection getSubjects() { ++ return Collections.unmodifiableCollection(subjects); ++ } ++ ++ /** ++ * Sets the subject of the message. The subject is a short description of ++ * message contents. ++ * ++ * @param subject the subject of the message. ++ */ ++ public void setSubject(String subject) { ++ if (subject == null) { ++ removeSubject(""); // use empty string because #removeSubject(null) is ambiguous ++ 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 the new {@link org.jivesoftware.smack.packet.Message.Subject} ++ * @throws NullPointerException if the subject is null, a null pointer exception is thrown ++ */ ++ public Subject addSubject(String language, String subject) { ++ language = determineLanguage(language); ++ Subject messageSubject = new Subject(language, subject); ++ subjects.add(messageSubject); ++ return messageSubject; ++ } ++ ++ /** ++ * Removes the subject with the given language from the 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. ++ */ ++ public boolean removeSubject(String language) { ++ language = determineLanguage(language); ++ for (Subject subject : subjects) { ++ if (language.equals(subject.language)) { ++ return subjects.remove(subject); ++ } ++ } ++ return false; ++ } ++ ++ /** ++ * Removes the subject from the message and returns true if the subject was removed. ++ * ++ * @param subject the subject being removed from the message. ++ * @return true if the subject was successfully removed and false if it was not. ++ */ ++ public boolean removeSubject(Subject subject) { ++ return subjects.remove(subject); ++ } ++ ++ /** ++ * Returns all the languages being used for the subjects, not including the default subject. ++ * ++ * @return the languages being used for the subjects. ++ */ ++ public Collection getSubjectLanguages() { ++ Subject defaultSubject = getMessageSubject(null); ++ List languages = new ArrayList(); ++ for (Subject subject : subjects) { ++ if (!subject.equals(defaultSubject)) { ++ languages.add(subject.language); ++ } ++ } ++ return Collections.unmodifiableCollection(languages); ++ } ++ ++ /** ++ * Returns the default body of the message, or null if the body has not been set. The body ++ * is the main message contents. ++ *

      ++ * The default body of a message is the body that corresponds to the message's language. ++ * (see {@link #getLanguage()}) or if no language is set to the applications default ++ * language (see {@link Packet#getDefaultLanguage()}). ++ * ++ * @return the body of the message. ++ */ ++ public String getBody() { ++ return getBody(null); ++ } ++ ++ /** ++ * Returns the body corresponding to the language. If the language is null, the method result ++ * will be the same as {@link #getBody()}. Null will be returned if the language does not have ++ * a corresponding body. ++ * ++ * @param language the language of the body to return. ++ * @return the body related to the passed in language. ++ * @since 3.0.2 ++ */ ++ public String getBody(String language) { ++ Body body = getMessageBody(language); ++ return body == null ? null : body.message; ++ } ++ ++ private Body getMessageBody(String language) { ++ language = determineLanguage(language); ++ for (Body body : bodies) { ++ if (language.equals(body.language)) { ++ return body; ++ } ++ } ++ return null; ++ } ++ ++ /** ++ * Returns a set of all bodies in this Message, including the default message body accessible ++ * from {@link #getBody()}. ++ * ++ * @return a collection of all bodies in this Message. ++ * @since 3.0.2 ++ */ ++ public Collection getBodies() { ++ return Collections.unmodifiableCollection(bodies); ++ } ++ ++ /** ++ * Sets the body of the message. The body is the main message contents. ++ * ++ * @param body the body of the message. ++ */ ++ public void setBody(String body) { ++ if (body == null) { ++ removeBody(""); // use empty string because #removeBody(null) is ambiguous ++ 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 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 ++ */ ++ public Body addBody(String language, String body) { ++ language = determineLanguage(language); ++ Body messageBody = new Body(language, body); ++ bodies.add(messageBody); ++ return messageBody; ++ } ++ ++ /** ++ * Removes the body with the given language from the 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. ++ */ ++ public boolean removeBody(String language) { ++ language = determineLanguage(language); ++ for (Body body : bodies) { ++ if (language.equals(body.language)) { ++ return bodies.remove(body); ++ } ++ } ++ return false; ++ } ++ ++ /** ++ * Removes the body from the message and returns true if the body was removed. ++ * ++ * @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 ++ */ ++ public boolean removeBody(Body body) { ++ return bodies.remove(body); ++ } ++ ++ /** ++ * Returns all the languages being used for the bodies, not including the default body. ++ * ++ * @return the languages being used for the bodies. ++ * @since 3.0.2 ++ */ ++ public Collection getBodyLanguages() { ++ Body defaultBody = getMessageBody(null); ++ List languages = new ArrayList(); ++ for (Body body : bodies) { ++ if (!body.equals(defaultBody)) { ++ languages.add(body.language); ++ } ++ } ++ return Collections.unmodifiableCollection(languages); ++ } ++ ++ /** ++ * Returns the thread id of the message, which is a unique identifier for a sequence ++ * of "chat" messages. If no thread id is set, null will be returned. ++ * ++ * @return the thread id of the message, or null if it doesn't exist. ++ */ ++ public String getThread() { ++ return thread; ++ } ++ ++ /** ++ * Sets the thread id of the message, which is a unique identifier for a sequence ++ * of "chat" messages. ++ * ++ * @param thread the thread id of the message. ++ */ ++ public void setThread(String thread) { ++ this.thread = thread; ++ } ++ ++ /** ++ * Returns the xml:lang of this Message. ++ * ++ * @return the xml:lang of this Message. ++ * @since 3.0.2 ++ */ ++ public String getLanguage() { ++ return language; ++ } ++ ++ /** ++ * Sets the xml:lang of this Message. ++ * ++ * @param language the xml:lang of this Message. ++ * @since 3.0.2 ++ */ ++ public void setLanguage(String language) { ++ this.language = language; ++ } ++ ++ private String determineLanguage(String language) { ++ ++ // empty string is passed by #setSubject() and #setBody() and is the same as null ++ language = "".equals(language) ? null : language; ++ ++ // if given language is null check if message language is set ++ if (language == null && this.language != null) { ++ return this.language; ++ } ++ else if (language == null) { ++ return getDefaultLanguage(); ++ } ++ else { ++ return language; ++ } ++ ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ // Add the subject in the default language ++ Subject defaultSubject = getMessageSubject(null); ++ if (defaultSubject != null) { ++ buf.append("").append(StringUtils.escapeForXML(defaultSubject.subject)).append(""); ++ } ++ // Add the subject in other languages ++ for (Subject subject : getSubjects()) { ++ // Skip the default language ++ if(subject.equals(defaultSubject)) ++ continue; ++ buf.append(""); ++ buf.append(StringUtils.escapeForXML(subject.subject)); ++ buf.append(""); ++ } ++ // Add the body in the default language ++ Body defaultBody = getMessageBody(null); ++ if (defaultBody != null) { ++ buf.append("").append(StringUtils.escapeForXML(defaultBody.message)).append(""); ++ } ++ // Add the bodies in other languages ++ for (Body body : getBodies()) { ++ // Skip the default language ++ if(body.equals(defaultBody)) ++ continue; ++ buf.append(""); ++ buf.append(StringUtils.escapeForXML(body.getMessage())); ++ buf.append(""); ++ } ++ if (thread != null) { ++ buf.append("").append(thread).append(""); ++ } ++ // Append the error subpacket if the message type is an error. ++ if (type == Type.error) { ++ XMPPError error = getError(); ++ if (error != null) { ++ buf.append(error.toXML()); ++ } ++ } ++ // Add packet extensions, if any are defined. ++ buf.append(getExtensionsXML()); ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ ++ public boolean equals(Object o) { ++ if (this == o) return true; ++ if (o == null || getClass() != o.getClass()) return false; ++ ++ Message message = (Message) o; ++ ++ if(!super.equals(message)) { return false; } ++ if (bodies.size() != message.bodies.size() || !bodies.containsAll(message.bodies)) { ++ return false; ++ } ++ if (language != null ? !language.equals(message.language) : message.language != null) { ++ return false; ++ } ++ if (subjects.size() != message.subjects.size() || !subjects.containsAll(message.subjects)) { ++ return false; ++ } ++ if (thread != null ? !thread.equals(message.thread) : message.thread != null) { ++ return false; ++ } ++ return type == message.type; ++ ++ } ++ ++ public int hashCode() { ++ int result; ++ result = (type != null ? type.hashCode() : 0); ++ result = 31 * result + subjects.hashCode(); ++ result = 31 * result + (thread != null ? thread.hashCode() : 0); ++ result = 31 * result + (language != null ? language.hashCode() : 0); ++ result = 31 * result + bodies.hashCode(); ++ return result; ++ } ++ ++ /** ++ * Represents a message subject, its language and the content of the subject. ++ */ ++ public static class Subject { ++ ++ private String subject; ++ private String language; ++ ++ private Subject(String language, String subject) { ++ if (language == null) { ++ throw new NullPointerException("Language cannot be null."); ++ } ++ if (subject == null) { ++ throw new NullPointerException("Subject cannot be null."); ++ } ++ this.language = language; ++ this.subject = subject; ++ } ++ ++ /** ++ * Returns the language of this message subject. ++ * ++ * @return the language of this message subject. ++ */ ++ public String getLanguage() { ++ return language; ++ } ++ ++ /** ++ * Returns the subject content. ++ * ++ * @return the content of the subject. ++ */ ++ public String getSubject() { ++ return subject; ++ } ++ ++ ++ public int hashCode() { ++ final int prime = 31; ++ int result = 1; ++ result = prime * result + this.language.hashCode(); ++ result = prime * result + this.subject.hashCode(); ++ return result; ++ } ++ ++ public boolean equals(Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ if (obj == null) { ++ return false; ++ } ++ if (getClass() != obj.getClass()) { ++ return false; ++ } ++ Subject other = (Subject) obj; ++ // simplified comparison because language and subject are always set ++ return this.language.equals(other.language) && this.subject.equals(other.subject); ++ } ++ ++ } ++ ++ /** ++ * Represents a message body, its language and the content of the message. ++ */ ++ public static class Body { ++ ++ private String message; ++ private String language; ++ ++ private Body(String language, String message) { ++ if (language == null) { ++ throw new NullPointerException("Language cannot be null."); ++ } ++ if (message == null) { ++ throw new NullPointerException("Message cannot be null."); ++ } ++ this.language = language; ++ this.message = message; ++ } ++ ++ /** ++ * Returns the language of this message body. ++ * ++ * @return the language of this message body. ++ */ ++ public String getLanguage() { ++ return language; ++ } ++ ++ /** ++ * Returns the message content. ++ * ++ * @return the content of the message. ++ */ ++ public String getMessage() { ++ return message; ++ } ++ ++ public int hashCode() { ++ final int prime = 31; ++ int result = 1; ++ result = prime * result + this.language.hashCode(); ++ result = prime * result + this.message.hashCode(); ++ return result; ++ } ++ ++ public boolean equals(Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ if (obj == null) { ++ return false; ++ } ++ if (getClass() != obj.getClass()) { ++ return false; ++ } ++ Body other = (Body) obj; ++ // simplified comparison because language and message are always set ++ return this.language.equals(other.language) && this.message.equals(other.message); ++ } ++ ++ } ++ ++ /** ++ * Represents the type of a message. ++ */ ++ public enum Type { ++ ++ /** ++ * (Default) a normal text message used in email like interface. ++ */ ++ normal, ++ ++ /** ++ * Typically short text message used in line-by-line chat interfaces. ++ */ ++ chat, ++ ++ /** ++ * Chat message sent to a groupchat server for group chats. ++ */ ++ groupchat, ++ ++ /** ++ * Text message to be displayed in scrolling marquee displays. ++ */ ++ headline, ++ ++ /** ++ * indicates a messaging error. ++ */ ++ error; ++ ++ public static Type fromString(String name) { ++ try { ++ return Type.valueOf(name); ++ } ++ catch (Exception e) { ++ return normal; ++ } ++ } ++ ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Packet.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Packet.java +new file mode 100644 +index 0000000..7309de8 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Packet.java +@@ -0,0 +1,482 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.io.ByteArrayOutputStream; ++import java.io.ObjectOutputStream; ++import java.io.Serializable; ++import java.text.DateFormat; ++import java.text.SimpleDateFormat; ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.List; ++import java.util.Map; ++import java.util.TimeZone; ++import java.util.concurrent.CopyOnWriteArrayList; ++ ++import org.jivesoftware.smack.util.StringUtils; ++ ++/** ++ * Base class for XMPP packets. Every packet has a unique ID (which is automatically ++ * generated, but can be overriden). Optionally, the "to" and "from" fields can be set, ++ * as well as an arbitrary number of properties. ++ * ++ * Properties provide an easy mechanism for clients to share data. Each property has a ++ * String name, and a value that is a Java primitive (int, long, float, double, boolean) ++ * or any Serializable object (a Java object is Serializable when it implements the ++ * Serializable interface). ++ * ++ * @author Matt Tucker ++ */ ++public abstract class Packet { ++ ++ protected static final String DEFAULT_LANGUAGE = ++ java.util.Locale.getDefault().getLanguage().toLowerCase(); ++ ++ private static String DEFAULT_XML_NS = null; ++ ++ /** ++ * Constant used as packetID to indicate that a packet has no id. To indicate that a packet ++ * has no id set this constant as the packet's id. When the packet is asked for its id the ++ * answer will be null. ++ */ ++ public static final String ID_NOT_AVAILABLE = "ID_NOT_AVAILABLE"; ++ ++ /** ++ * Date format as defined in XEP-0082 - XMPP Date and Time Profiles. ++ * The time zone is set to UTC. ++ *

      ++ * Date formats are not synchronized. Since multiple threads access the format concurrently, ++ * it must be synchronized externally. ++ */ ++ public static final DateFormat XEP_0082_UTC_FORMAT = new SimpleDateFormat( ++ "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); ++ static { ++ XEP_0082_UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); ++ } ++ ++ ++ /** ++ * A prefix helps to make sure that ID's are unique across mutliple instances. ++ */ ++ private static String prefix = StringUtils.randomString(5) + "-"; ++ ++ /** ++ * Keeps track of the current increment, which is appended to the prefix to ++ * forum a unique ID. ++ */ ++ private static long id = 0; ++ ++ private String xmlns = DEFAULT_XML_NS; ++ ++ /** ++ * Returns the next unique id. Each id made up of a short alphanumeric ++ * prefix along with a unique numeric value. ++ * ++ * @return the next id. ++ */ ++ public static synchronized String nextID() { ++ return prefix + Long.toString(id++); ++ } ++ ++ public static void setDefaultXmlns(String defaultXmlns) { ++ DEFAULT_XML_NS = defaultXmlns; ++ } ++ ++ private String packetID = null; ++ private String to = null; ++ private String from = null; ++ private final List packetExtensions ++ = new CopyOnWriteArrayList(); ++ ++ private final Map properties = new HashMap(); ++ private XMPPError error = null; ++ ++ /** ++ * Returns the unique ID of the packet. The returned value could be null when ++ * ID_NOT_AVAILABLE was set as the packet's id. ++ * ++ * @return the packet's unique ID or null if the packet's id is not available. ++ */ ++ public String getPacketID() { ++ if (ID_NOT_AVAILABLE.equals(packetID)) { ++ return null; ++ } ++ ++ if (packetID == null) { ++ packetID = nextID(); ++ } ++ return packetID; ++ } ++ ++ /** ++ * Sets the unique ID of the packet. To indicate that a packet has no id ++ * pass the constant ID_NOT_AVAILABLE as the packet's id value. ++ * ++ * @param packetID the unique ID for the packet. ++ */ ++ public void setPacketID(String packetID) { ++ this.packetID = packetID; ++ } ++ ++ /** ++ * Returns who the packet is being sent "to", or null if ++ * the value is not set. The XMPP protocol often makes the "to" ++ * attribute optional, so it does not always need to be set.

      ++ * ++ * The StringUtils class provides several useful methods for dealing with ++ * XMPP addresses such as parsing the ++ * {@link StringUtils#parseBareAddress(String) bare address}, ++ * {@link StringUtils#parseName(String) user name}, ++ * {@link StringUtils#parseServer(String) server}, and ++ * {@link StringUtils#parseResource(String) resource}. ++ * ++ * @return who the packet is being sent to, or null if the ++ * value has not been set. ++ */ ++ public String getTo() { ++ return to; ++ } ++ ++ /** ++ * Sets who the packet 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 packet is being sent to. ++ */ ++ public void setTo(String to) { ++ this.to = to; ++ } ++ ++ /** ++ * Returns who the packet is being sent "from" or null if ++ * the value is not set. The XMPP protocol often makes the "from" ++ * attribute optional, so it does not always need to be set.

      ++ * ++ * The StringUtils class provides several useful methods for dealing with ++ * XMPP addresses such as parsing the ++ * {@link StringUtils#parseBareAddress(String) bare address}, ++ * {@link StringUtils#parseName(String) user name}, ++ * {@link StringUtils#parseServer(String) server}, and ++ * {@link StringUtils#parseResource(String) resource}. ++ * ++ * @return who the packet is being sent from, or null if the ++ * value has not been set. ++ */ ++ public String getFrom() { ++ return from; ++ } ++ ++ /** ++ * Sets who the packet 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 packet is being sent to. ++ */ ++ public void setFrom(String from) { ++ this.from = from; ++ } ++ ++ /** ++ * Returns the error associated with this packet, or null if there are ++ * no errors. ++ * ++ * @return the error sub-packet or null if there isn't an error. ++ */ ++ public XMPPError getError() { ++ return error; ++ } ++ ++ /** ++ * Sets the error for this packet. ++ * ++ * @param error the error to associate with this packet. ++ */ ++ public void setError(XMPPError error) { ++ this.error = error; ++ } ++ ++ /** ++ * Returns an unmodifiable collection of the packet extensions attached to the packet. ++ * ++ * @return the packet extensions. ++ */ ++ public synchronized Collection getExtensions() { ++ if (packetExtensions == null) { ++ return Collections.emptyList(); ++ } ++ return Collections.unmodifiableList(new ArrayList(packetExtensions)); ++ } ++ ++ /** ++ * Returns the first extension of this packet that has the given namespace. ++ * ++ * @param namespace the namespace of the extension that is desired. ++ * @return the packet extension with the given namespace. ++ */ ++ public PacketExtension getExtension(String namespace) { ++ return getExtension(null, namespace); ++ } ++ ++ /** ++ * Returns the first packet extension that matches the specified element name and ++ * namespace, or null if it doesn't exist. If the provided elementName is null ++ * than only the provided namespace is attempted to be matched. Packet extensions are ++ * are arbitrary XML sub-documents in standard XMPP packets. By default, a ++ * DefaultPacketExtension instance will be returned for each extension. However, ++ * PacketExtensionProvider instances can be registered with the ++ * {@link org.jivesoftware.smack.provider.ProviderManager ProviderManager} ++ * class to handle custom parsing. In that case, the type of the Object ++ * will be determined by the provider. ++ * ++ * @param elementName the XML element name of the packet extension. (May be null) ++ * @param namespace the XML element namespace of the packet extension. ++ * @return the extension, or null if it doesn't exist. ++ */ ++ public PacketExtension getExtension(String elementName, String namespace) { ++ if (namespace == null) { ++ return null; ++ } ++ for (PacketExtension ext : packetExtensions) { ++ if ((elementName == null || elementName.equals(ext.getElementName())) ++ && namespace.equals(ext.getNamespace())) ++ { ++ return ext; ++ } ++ } ++ return null; ++ } ++ ++ /** ++ * Adds a packet extension to the packet. ++ * ++ * @param extension a packet extension. ++ */ ++ public void addExtension(PacketExtension extension) { ++ packetExtensions.add(extension); ++ } ++ ++ /** ++ * Removes a packet extension from the packet. ++ * ++ * @param extension the packet extension to remove. ++ */ ++ public void removeExtension(PacketExtension extension) { ++ packetExtensions.remove(extension); ++ } ++ ++ /** ++ * Returns the packet property with the specified name or null if the ++ * property doesn't exist. Property values that were orginally primitives will ++ * be returned as their object equivalent. For example, an int property will be ++ * returned as an Integer, a double as a Double, etc. ++ * ++ * @param name the name of the property. ++ * @return the property, or null if the property doesn't exist. ++ */ ++ public synchronized Object getProperty(String name) { ++ if (properties == null) { ++ return null; ++ } ++ return properties.get(name); ++ } ++ ++ /** ++ * Sets a property with an Object as the value. The value must be Serializable ++ * or an IllegalArgumentException will be thrown. ++ * ++ * @param name the name of the property. ++ * @param value the value of the property. ++ */ ++ public synchronized void setProperty(String name, Object value) { ++ if (!(value instanceof Serializable)) { ++ throw new IllegalArgumentException("Value must be serialiazble"); ++ } ++ properties.put(name, value); ++ } ++ ++ /** ++ * Deletes a property. ++ * ++ * @param name the name of the property to delete. ++ */ ++ public synchronized void deleteProperty(String name) { ++ if (properties == null) { ++ return; ++ } ++ properties.remove(name); ++ } ++ ++ /** ++ * Returns an unmodifiable collection of all the property names that are set. ++ * ++ * @return all property names. ++ */ ++ public synchronized Collection getPropertyNames() { ++ if (properties == null) { ++ return Collections.emptySet(); ++ } ++ return Collections.unmodifiableSet(new HashSet(properties.keySet())); ++ } ++ ++ /** ++ * Returns the packet as XML. Every concrete extension of Packet must implement ++ * this method. In addition to writing out packet-specific data, every sub-class ++ * should also write out the error and the extensions data if they are defined. ++ * ++ * @return the XML format of the packet as a String. ++ */ ++ public abstract String toXML(); ++ ++ /** ++ * Returns the extension sub-packets (including properties data) as an XML ++ * String, or the Empty String if there are no packet extensions. ++ * ++ * @return the extension sub-packets as XML or the Empty String if there ++ * are no packet extensions. ++ */ ++ protected synchronized String getExtensionsXML() { ++ StringBuilder buf = new StringBuilder(); ++ // Add in all standard extension sub-packets. ++ for (PacketExtension extension : getExtensions()) { ++ buf.append(extension.toXML()); ++ } ++ // Add in packet properties. ++ if (properties != null && !properties.isEmpty()) { ++ buf.append(""); ++ // Loop through all properties and write them out. ++ for (String name : getPropertyNames()) { ++ Object value = getProperty(name); ++ buf.append(""); ++ buf.append("").append(StringUtils.escapeForXML(name)).append(""); ++ buf.append("").append(value).append(""); ++ } ++ else if (value instanceof Long) { ++ buf.append("long\">").append(value).append(""); ++ } ++ else if (value instanceof Float) { ++ buf.append("float\">").append(value).append(""); ++ } ++ else if (value instanceof Double) { ++ buf.append("double\">").append(value).append(""); ++ } ++ else if (value instanceof Boolean) { ++ buf.append("boolean\">").append(value).append(""); ++ } ++ else if (value instanceof String) { ++ buf.append("string\">"); ++ buf.append(StringUtils.escapeForXML((String)value)); ++ buf.append(""); ++ } ++ // Otherwise, it's a generic Serializable object. Serialized objects are in ++ // a binary format, which won't work well inside of XML. Therefore, we base-64 ++ // encode the binary data before adding it. ++ else { ++ ByteArrayOutputStream byteStream = null; ++ ObjectOutputStream out = null; ++ try { ++ byteStream = new ByteArrayOutputStream(); ++ out = new ObjectOutputStream(byteStream); ++ out.writeObject(value); ++ buf.append("java-object\">"); ++ String encodedVal = StringUtils.encodeBase64(byteStream.toByteArray()); ++ buf.append(encodedVal).append(""); ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ } ++ finally { ++ if (out != null) { ++ try { ++ out.close(); ++ } ++ catch (Exception e) { ++ // Ignore. ++ } ++ } ++ if (byteStream != null) { ++ try { ++ byteStream.close(); ++ } ++ catch (Exception e) { ++ // Ignore. ++ } ++ } ++ } ++ } ++ buf.append(""); ++ } ++ buf.append(""); ++ } ++ return buf.toString(); ++ } ++ ++ public String getXmlns() { ++ return this.xmlns; ++ } ++ ++ /** ++ * Returns the default language used for all messages containing localized content. ++ * ++ * @return the default language ++ */ ++ public static String getDefaultLanguage() { ++ return DEFAULT_LANGUAGE; ++ } ++ ++ public boolean equals(Object o) { ++ if (this == o) return true; ++ if (o == null || getClass() != o.getClass()) return false; ++ ++ Packet packet = (Packet) o; ++ ++ if (error != null ? !error.equals(packet.error) : packet.error != null) { return false; } ++ if (from != null ? !from.equals(packet.from) : packet.from != null) { return false; } ++ if (!packetExtensions.equals(packet.packetExtensions)) { return false; } ++ if (packetID != null ? !packetID.equals(packet.packetID) : packet.packetID != null) { ++ return false; ++ } ++ if (properties != null ? !properties.equals(packet.properties) ++ : packet.properties != null) { ++ return false; ++ } ++ if (to != null ? !to.equals(packet.to) : packet.to != null) { return false; } ++ return !(xmlns != null ? !xmlns.equals(packet.xmlns) : packet.xmlns != null); ++ } ++ ++ public int hashCode() { ++ int result; ++ result = (xmlns != null ? xmlns.hashCode() : 0); ++ result = 31 * result + (packetID != null ? packetID.hashCode() : 0); ++ result = 31 * result + (to != null ? to.hashCode() : 0); ++ result = 31 * result + (from != null ? from.hashCode() : 0); ++ result = 31 * result + packetExtensions.hashCode(); ++ result = 31 * result + properties.hashCode(); ++ result = 31 * result + (error != null ? error.hashCode() : 0); ++ return result; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/PacketExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/PacketExtension.java +new file mode 100644 +index 0000000..d2afbf8 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/PacketExtension.java +@@ -0,0 +1,56 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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; ++ ++/** ++ * Interface to represent packet extensions. A packet extension is an XML subdocument ++ * with a root element name and namespace. Packet extensions are used to provide ++ * extended functionality beyond what is in the base XMPP specification. Examples of ++ * packet extensions include message events, message properties, and extra presence data. ++ * IQ packets cannot contain packet extensions. ++ * ++ * @see DefaultPacketExtension ++ * @see org.jivesoftware.smack.provider.PacketExtensionProvider ++ * @author Matt Tucker ++ */ ++public interface PacketExtension { ++ ++ /** ++ * Returns the root element name. ++ * ++ * @return the element name. ++ */ ++ public String getElementName(); ++ ++ /** ++ * Returns the root element XML namespace. ++ * ++ * @return the namespace. ++ */ ++ public String getNamespace(); ++ ++ /** ++ * Returns the XML representation of the PacketExtension. ++ * ++ * @return the packet extension as XML. ++ */ ++ public String toXML(); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Presence.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Presence.java +new file mode 100644 +index 0000000..97402a0 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Presence.java +@@ -0,0 +1,358 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.util.StringUtils; ++ ++/** ++ * Represents XMPP presence packets. Every presence packet has a type, which is one of ++ * the following values: ++ *

        ++ *
      • {@link Presence.Type#available available} -- (Default) indicates the user is available to ++ * receive messages. ++ *
      • {@link Presence.Type#unavailable unavailable} -- the user is unavailable to receive messages. ++ *
      • {@link Presence.Type#subscribe subscribe} -- request subscription to recipient's presence. ++ *
      • {@link Presence.Type#subscribed subscribed} -- grant subscription to sender's presence. ++ *
      • {@link Presence.Type#unsubscribe unsubscribe} -- request removal of subscription to ++ * sender's presence. ++ *
      • {@link Presence.Type#unsubscribed unsubscribed} -- grant removal of subscription to ++ * sender's presence. ++ *
      • {@link Presence.Type#error error} -- the presence packet contains an error message. ++ *

      ++ * ++ * A number of attributes are optional: ++ *

        ++ *
      • Status -- free-form text describing a user's presence (i.e., gone to lunch). ++ *
      • Priority -- non-negative numerical priority of a sender's resource. The ++ * highest resource priority is the default recipient of packets not addressed ++ * to a particular resource. ++ *
      • Mode -- one of five presence modes: {@link Mode#available available} (the default), ++ * {@link Mode#chat chat}, {@link Mode#away away}, {@link Mode#xa xa} (extended away), and ++ * {@link Mode#dnd dnd} (do not disturb). ++ *

      ++ * ++ * Presence packets are used for two purposes. First, to notify the server of our ++ * the clients current presence status. Second, they are used to subscribe and ++ * unsubscribe users from the roster. ++ * ++ * @see RosterPacket ++ * @author Matt Tucker ++ */ ++public class Presence extends Packet { ++ ++ private Type type = Type.available; ++ private String status = null; ++ private int priority = Integer.MIN_VALUE; ++ private Mode mode = null; ++ private String language; ++ ++ /** ++ * Creates a new presence update. Status, priority, and mode are left un-set. ++ * ++ * @param type the type. ++ */ ++ public Presence(Type type) { ++ setType(type); ++ } ++ ++ /** ++ * Creates a new presence update with a specified status, priority, and mode. ++ * ++ * @param type the type. ++ * @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. ++ */ ++ public Presence(Type type, String status, int priority, Mode mode) { ++ setType(type); ++ setStatus(status); ++ setPriority(priority); ++ setMode(mode); ++ } ++ ++ /** ++ * Returns true if the {@link Type presence type} is available (online) and ++ * false if the user is unavailable (offline), or if this is a presence packet ++ * involved in a subscription operation. This is a convenience method ++ * equivalent to getType() == Presence.Type.available. Note that even ++ * when the user is available, their presence mode may be {@link Mode#away away}, ++ * {@link Mode#xa extended away} or {@link Mode#dnd do not disturb}. Use ++ * {@link #isAway()} to determine if the user is away. ++ * ++ * @return true if the presence type is available. ++ */ ++ public boolean isAvailable() { ++ return type == Type.available; ++ } ++ ++ /** ++ * Returns true if the presence type is {@link Type#available available} and the presence ++ * mode is {@link Mode#away away}, {@link Mode#xa extended away}, or ++ * {@link Mode#dnd do not disturb}. False will be returned when the type or mode ++ * is any other value, including when the presence type is unavailable (offline). ++ * This is a convenience method equivalent to ++ * type == Type.available && (mode == Mode.away || mode == Mode.xa || mode == Mode.dnd). ++ * ++ * @return true if the presence type is available and the presence mode is away, xa, or dnd. ++ */ ++ public boolean isAway() { ++ 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. ++ */ ++ public Type getType() { ++ return type; ++ } ++ ++ /** ++ * Sets the type of the presence packet. ++ * ++ * @param type the type of the presence packet. ++ */ ++ public void setType(Type type) { ++ if(type == null) { ++ throw new NullPointerException("Type cannot be null"); ++ } ++ this.type = type; ++ } ++ ++ /** ++ * Returns the status message of the presence update, or null 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. ++ */ ++ public String getStatus() { ++ return status; ++ } ++ ++ /** ++ * Sets the status message of the presence update. The status is free-form text ++ * describing a user's presence (i.e., "gone to lunch"). ++ * ++ * @param status the status message. ++ */ ++ public void setStatus(String status) { ++ this.status = status; ++ } ++ ++ /** ++ * Returns the priority of the presence, or Integer.MIN_VALUE if no priority has been set. ++ * ++ * @return the priority. ++ */ ++ public int getPriority() { ++ return priority; ++ } ++ ++ /** ++ * Sets the priority of the presence. The valid range is -128 through 128. ++ * ++ * @param priority the priority of the presence. ++ * @throws IllegalArgumentException if the priority is outside the valid range. ++ */ ++ public void setPriority(int priority) { ++ if (priority < -128 || priority > 128) { ++ throw new IllegalArgumentException("Priority value " + priority + ++ " is not valid. Valid range is -128 through 128."); ++ } ++ this.priority = priority; ++ } ++ ++ /** ++ * Returns the mode of the presence update, or null if the mode is not set. ++ * A null presence mode value is interpreted to be the same thing as ++ * {@link Presence.Mode#available}. ++ * ++ * @return the mode. ++ */ ++ public Mode getMode() { ++ return mode; ++ } ++ ++ /** ++ * Sets the mode of the presence update. A null presence mode value is interpreted ++ * to be the same thing as {@link Presence.Mode#available}. ++ * ++ * @param mode the mode. ++ */ ++ public void setMode(Mode mode) { ++ this.mode = mode; ++ } ++ ++ /** ++ * Returns the xml:lang of this Presence, or null if one has not been set. ++ * ++ * @return the xml:lang of this Presence, or null if one has not been set. ++ * @since 3.0.2 ++ */ ++ private String getLanguage() { ++ return language; ++ } ++ ++ /** ++ * Sets the xml:lang of this Presence. ++ * ++ * @param language the xml:lang of this Presence. ++ * @since 3.0.2 ++ */ ++ public void setLanguage(String language) { ++ this.language = language; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ if (status != null) { ++ buf.append("").append(StringUtils.escapeForXML(status)).append(""); ++ } ++ if (priority != Integer.MIN_VALUE) { ++ buf.append("").append(priority).append(""); ++ } ++ if (mode != null && mode != Mode.available) { ++ buf.append("").append(mode).append(""); ++ } ++ ++ buf.append(this.getExtensionsXML()); ++ ++ // Add the error sub-packet, if there is one. ++ XMPPError error = getError(); ++ if (error != null) { ++ buf.append(error.toXML()); ++ } ++ ++ buf.append(""); ++ ++ return buf.toString(); ++ } ++ ++ public String toString() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(type); ++ if (mode != null) { ++ buf.append(": ").append(mode); ++ } ++ if (getStatus() != null) { ++ buf.append(" (").append(getStatus()).append(")"); ++ } ++ return buf.toString(); ++ } ++ ++ /** ++ * A enum to represent the presecence type. Not that presence type is often confused ++ * with presence mode. Generally, if a user is signed into a server, they have a presence ++ * type of {@link #available available}, even if the mode is {@link Mode#away away}, ++ * {@link Mode#dnd dnd}, etc. The presence type is only {@link #unavailable unavailable} when ++ * the user is signing out of the server. ++ */ ++ public enum Type { ++ ++ /** ++ * The user is available to receive messages (default). ++ */ ++ available, ++ ++ /** ++ * The user is unavailable to receive messages. ++ */ ++ unavailable, ++ ++ /** ++ * Request subscription to recipient's presence. ++ */ ++ subscribe, ++ ++ /** ++ * Grant subscription to sender's presence. ++ */ ++ subscribed, ++ ++ /** ++ * Request removal of subscription to sender's presence. ++ */ ++ unsubscribe, ++ ++ /** ++ * Grant removal of subscription to sender's presence. ++ */ ++ unsubscribed, ++ ++ /** ++ * The presence packet contains an error message. ++ */ ++ error ++ } ++ ++ /** ++ * An enum to represent the presence mode. ++ */ ++ public enum Mode { ++ ++ /** ++ * Free to chat. ++ */ ++ chat, ++ ++ /** ++ * Available (the default). ++ */ ++ available, ++ ++ /** ++ * Away. ++ */ ++ away, ++ ++ /** ++ * Away for an extended period of time. ++ */ ++ xa, ++ ++ /** ++ * Do not disturb. ++ */ ++ dnd ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Privacy.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Privacy.java +new file mode 100644 +index 0000000..57162cb +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Privacy.java +@@ -0,0 +1,323 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2006-2007 Jive Software. ++ * ++ * All rights reserved. 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.*; ++ ++/** ++ * A Privacy IQ Packet, is used by the {@link org.jivesoftware.smack.PrivacyListManager} ++ * and {@link org.jivesoftware.smack.provider.PrivacyProvider} to allow and block ++ * communications from other users. It contains the appropriate structure to suit ++ * user-defined privacy lists. Different configured Privacy packages are used in the ++ * server & manager communication in order to: ++ *

        ++ *
      • Retrieving one's privacy lists. ++ *
      • Adding, removing, and editing one's privacy lists. ++ *
      • Setting, changing, or declining active lists. ++ *
      • Setting, changing, or declining the default list (i.e., the list that is active by default). ++ *
      ++ * Privacy Items can handle different kind of blocking communications based on JID, group, ++ * subscription type or globally {@link PrivacyItem} ++ * ++ * @author Francisco Vives ++ */ ++public class Privacy extends IQ { ++ /** declineActiveList is true when the user declines the use of the active list **/ ++ private boolean declineActiveList=false; ++ /** activeName is the name associated with the active list set for the session **/ ++ private String activeName; ++ /** declineDefaultList is true when the user declines the use of the default list **/ ++ private boolean declineDefaultList=false; ++ /** defaultName is the name of the default list that applies to the user as a whole **/ ++ private String defaultName; ++ /** itemLists holds the set of privacy items classified in lists. It is a map where the ++ * key is the name of the list and the value a collection with privacy items. **/ ++ private Map> itemLists = new HashMap>(); ++ ++ /** ++ * Set or update a privacy list with privacy items. ++ * ++ * @param listName the name of the new privacy list. ++ * @param listItem the {@link PrivacyItem} that rules the list. ++ * @return the privacy List. ++ */ ++ public List setPrivacyList(String listName, List listItem) { ++ // Add new list to the itemLists ++ this.getItemLists().put(listName, listItem); ++ return listItem; ++ } ++ ++ /** ++ * Set the active list based on the default list. ++ * ++ * @return the active List. ++ */ ++ public List setActivePrivacyList() { ++ this.setActiveName(this.getDefaultName()); ++ return this.getItemLists().get(this.getActiveName()); ++ } ++ ++ /** ++ * Deletes an existing privacy list. If the privacy list being deleted was the default list ++ * then the user will end up with no default list. Therefore, the user will have to set a new ++ * default list. ++ * ++ * @param listName the name of the list being deleted. ++ */ ++ public void deletePrivacyList(String listName) { ++ // Remove the list from the cache ++ this.getItemLists().remove(listName); ++ ++ // Check if deleted list was the default list ++ if (this.getDefaultName() != null && listName.equals(this.getDefaultName())) { ++ this.setDefaultName(null); ++ } ++ } ++ ++ /** ++ * Returns the active privacy list or null if none was found. ++ * ++ * @return list with {@link PrivacyItem} or null if none was found. ++ */ ++ public List getActivePrivacyList() { ++ // Check if we have the default list ++ if (this.getActiveName() == null) { ++ return null; ++ } else { ++ return this.getItemLists().get(this.getActiveName()); ++ } ++ } ++ ++ /** ++ * Returns the default privacy list or null if none was found. ++ * ++ * @return list with {@link PrivacyItem} or null if none was found. ++ */ ++ public List getDefaultPrivacyList() { ++ // Check if we have the default list ++ if (this.getDefaultName() == null) { ++ return null; ++ } else { ++ return this.getItemLists().get(this.getDefaultName()); ++ } ++ } ++ ++ /** ++ * Returns a specific privacy list. ++ * ++ * @param listName the name of the list to get. ++ * @return a List with {@link PrivacyItem} ++ */ ++ public List getPrivacyList(String listName) { ++ return this.getItemLists().get(listName); ++ } ++ ++ /** ++ * Returns the privacy item in the specified order. ++ * ++ * @param listName the name of the privacy list. ++ * @param order the order of the element. ++ * @return a List with {@link PrivacyItem} ++ */ ++ public PrivacyItem getItem(String listName, int order) { ++ Iterator values = getPrivacyList(listName).iterator(); ++ PrivacyItem itemFound = null; ++ while (itemFound == null && values.hasNext()) { ++ PrivacyItem element = values.next(); ++ if (element.getOrder() == order) { ++ itemFound = element; ++ } ++ } ++ return itemFound; ++ } ++ ++ /** ++ * Sets a given privacy list as the new user default list. ++ * ++ * @param newDefault the new default privacy list. ++ * @return if the default list was changed. ++ */ ++ public boolean changeDefaultList(String newDefault) { ++ if (this.getItemLists().containsKey(newDefault)) { ++ this.setDefaultName(newDefault); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ ++ /** ++ * Remove the list. ++ * ++ * @param listName name of the list to remove. ++ */ ++ public void deleteList(String listName) { ++ this.getItemLists().remove(listName); ++ } ++ ++ /** ++ * Returns the name associated with the active list set for the session. Communications ++ * will be verified against the active list. ++ * ++ * @return the name of the active list. ++ */ ++ public String getActiveName() { ++ return activeName; ++ } ++ ++ /** ++ * Sets the name associated with the active list set for the session. Communications ++ * will be verified against the active list. ++ * ++ * @param activeName is the name of the active list. ++ */ ++ public void setActiveName(String activeName) { ++ this.activeName = activeName; ++ } ++ ++ /** ++ * Returns the name of the default list that applies to the user as a whole. Default list is ++ * processed if there is no active list set for the target session/resource to which a stanza ++ * is addressed, or if there are no current sessions for the user. ++ * ++ * @return the name of the default list. ++ */ ++ public String getDefaultName() { ++ return defaultName; ++ } ++ ++ /** ++ * Sets the name of the default list that applies to the user as a whole. Default list is ++ * processed if there is no active list set for the target session/resource to which a stanza ++ * is addressed, or if there are no current sessions for the user. ++ * ++ * If there is no default list set, then all Privacy Items are processed. ++ * ++ * @param defaultName is the name of the default list. ++ */ ++ public void setDefaultName(String defaultName) { ++ this.defaultName = defaultName; ++ } ++ ++ /** ++ * Returns the collection of privacy list that the user holds. A Privacy List contains a set of ++ * rules that define if communication with the list owner is allowed or denied. ++ * Users may have zero, one or more privacy items. ++ * ++ * @return a map where the key is the name of the list and the value the ++ * collection of privacy items. ++ */ ++ public Map> getItemLists() { ++ return itemLists; ++ } ++ ++ /** ++ * Returns whether the receiver allows or declines the use of an active list. ++ * ++ * @return the decline status of the list. ++ */ ++ public boolean isDeclineActiveList() { ++ return declineActiveList; ++ } ++ ++ /** ++ * Sets whether the receiver allows or declines the use of an active list. ++ * ++ * @param declineActiveList indicates if the receiver declines the use of an active list. ++ */ ++ public void setDeclineActiveList(boolean declineActiveList) { ++ this.declineActiveList = declineActiveList; ++ } ++ ++ /** ++ * Returns whether the receiver allows or declines the use of a default list. ++ * ++ * @return the decline status of the list. ++ */ ++ public boolean isDeclineDefaultList() { ++ return declineDefaultList; ++ } ++ ++ /** ++ * Sets whether the receiver allows or declines the use of a default list. ++ * ++ * @param declineDefaultList indicates if the receiver declines the use of a default list. ++ */ ++ public void setDeclineDefaultList(boolean declineDefaultList) { ++ this.declineDefaultList = declineDefaultList; ++ } ++ ++ /** ++ * Returns all the list names the user has defined to group restrictions. ++ * ++ * @return a Set with Strings containing every list names. ++ */ ++ public Set getPrivacyListNames() { ++ return this.itemLists.keySet(); ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ ++ // Add the active tag ++ if (this.isDeclineActiveList()) { ++ buf.append(""); ++ } else { ++ if (this.getActiveName() != null) { ++ buf.append(""); ++ } ++ } ++ // Add the default tag ++ if (this.isDeclineDefaultList()) { ++ buf.append(""); ++ } else { ++ if (this.getDefaultName() != null) { ++ buf.append(""); ++ } ++ } ++ ++ // Add the list with their privacy items ++ for (Map.Entry> entry : this.getItemLists().entrySet()) { ++ String listName = entry.getKey(); ++ List items = entry.getValue(); ++ // Begin the list tag ++ if (items.isEmpty()) { ++ buf.append(""); ++ } else { ++ buf.append(""); ++ } ++ for (PrivacyItem item : items) { ++ // Append the item xml representation ++ buf.append(item.toXML()); ++ } ++ // Close the list tag ++ if (!items.isEmpty()) { ++ buf.append(""); ++ } ++ } ++ ++ // Add packet extensions, if any are defined. ++ buf.append(getExtensionsXML()); ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/PrivacyItem.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/PrivacyItem.java +new file mode 100644 +index 0000000..78cb8db +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/PrivacyItem.java +@@ -0,0 +1,445 @@ ++package org.jivesoftware.smack.packet; ++ ++/** ++ * A privacy item acts a rule that when matched defines if a packet should be blocked or not. ++ * ++ * Privacy Items can handle different kind of blocking communications based on JID, group, ++ * subscription type or globally by:
        ++ *
      • Allowing or blocking messages. ++ *
      • Allowing or blocking inbound presence notifications. ++ *
      • Allowing or blocking outbound presence notifications. ++ *
      • Allowing or blocking IQ stanzas. ++ *
      • Allowing or blocking all communications. ++ *
      ++ * @author Francisco Vives ++ */ ++public class PrivacyItem { ++ /** allow is the action associated with the item, it can allow or deny the communication. */ ++ private boolean allow; ++ /** order is a non-negative integer that is unique among all items in the list. */ ++ private int order; ++ /** rule hold the kind of communication ([jid|group|subscription]) it will allow or block and ++ * identifier to apply the action. ++ * If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID. ++ * If the type is "group", then the 'value' attribute SHOULD contain the name of a group ++ * in the user's roster. ++ * If the type is "subscription", then the 'value' attribute MUST be one of "both", "to", ++ * "from", or "none". */ ++ private PrivacyRule rule; ++ ++ /** blocks incoming IQ stanzas. */ ++ private boolean filterIQ = false; ++ /** filterMessage blocks incoming message stanzas. */ ++ private boolean filterMessage = false; ++ /** blocks incoming presence notifications. */ ++ private boolean filterPresence_in = false; ++ /** blocks outgoing presence notifications. */ ++ private boolean filterPresence_out = false; ++ ++ /** ++ * Creates a new privacy item. ++ * ++ * @param type the type. ++ */ ++ public PrivacyItem(String type, boolean allow, int order) { ++ this.setRule(PrivacyRule.fromString(type)); ++ this.setAllow(allow); ++ this.setOrder(order); ++ } ++ ++ /** ++ * Returns the action associated with the item, it MUST be filled and will allow or deny ++ * the communication. ++ * ++ * @return the allow communication status. ++ */ ++ public boolean isAllow() { ++ return allow; ++ } ++ ++ /** ++ * Sets the action associated with the item, it can allow or deny the communication. ++ * ++ * @param allow indicates if the receiver allow or deny the communication. ++ */ ++ private void setAllow(boolean allow) { ++ this.allow = allow; ++ } ++ ++ ++ /** ++ * Returns whether the receiver allow or deny incoming IQ stanzas or not. ++ * ++ * @return the iq filtering status. ++ */ ++ public boolean isFilterIQ() { ++ return filterIQ; ++ } ++ ++ ++ /** ++ * Sets whether the receiver allows or denies incoming IQ stanzas or not. ++ * ++ * @param filterIQ indicates if the receiver allows or denies incoming IQ stanzas. ++ */ ++ public void setFilterIQ(boolean filterIQ) { ++ this.filterIQ = filterIQ; ++ } ++ ++ ++ /** ++ * Returns whether the receiver allows or denies incoming messages or not. ++ * ++ * @return the message filtering status. ++ */ ++ public boolean isFilterMessage() { ++ return filterMessage; ++ } ++ ++ ++ /** ++ * Sets wheather the receiver allows or denies incoming messages or not. ++ * ++ * @param filterMessage indicates if the receiver allows or denies incoming messages or not. ++ */ ++ public void setFilterMessage(boolean filterMessage) { ++ this.filterMessage = filterMessage; ++ } ++ ++ ++ /** ++ * Returns whether the receiver allows or denies incoming presence or not. ++ * ++ * @return the iq filtering incoming presence status. ++ */ ++ public boolean isFilterPresence_in() { ++ return filterPresence_in; ++ } ++ ++ ++ /** ++ * Sets whether the receiver allows or denies incoming presence or not. ++ * ++ * @param filterPresence_in indicates if the receiver allows or denies filtering incoming presence. ++ */ ++ public void setFilterPresence_in(boolean filterPresence_in) { ++ this.filterPresence_in = filterPresence_in; ++ } ++ ++ ++ /** ++ * Returns whether the receiver allows or denies incoming presence or not. ++ * ++ * @return the iq filtering incoming presence status. ++ */ ++ public boolean isFilterPresence_out() { ++ return filterPresence_out; ++ } ++ ++ ++ /** ++ * Sets whether the receiver allows or denies outgoing presence or not. ++ * ++ * @param filterPresence_out indicates if the receiver allows or denies filtering outgoing presence ++ */ ++ public void setFilterPresence_out(boolean filterPresence_out) { ++ this.filterPresence_out = filterPresence_out; ++ } ++ ++ ++ /** ++ * Returns the order where the receiver is processed. List items are processed in ++ * ascending order. ++ * ++ * The order MUST be filled and its value MUST be a non-negative integer ++ * that is unique among all items in the list. ++ * ++ * @return the order number. ++ */ ++ public int getOrder() { ++ return order; ++ } ++ ++ ++ /** ++ * Sets the order where the receiver is processed. ++ * ++ * The order MUST be filled and its value MUST be a non-negative integer ++ * that is unique among all items in the list. ++ * ++ * @param order indicates the order in the list. ++ */ ++ public void setOrder(int order) { ++ this.order = order; ++ } ++ ++ /** ++ * Sets the element identifier to apply the action. ++ * ++ * If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID. ++ * If the type is "group", then the 'value' attribute SHOULD contain the name of a group ++ * in the user's roster. ++ * If the type is "subscription", then the 'value' attribute MUST be one of "both", "to", ++ * "from", or "none". ++ * ++ * @param value is the identifier to apply the action. ++ */ ++ public void setValue(String value) { ++ if (!(this.getRule() == null && value == null)) { ++ this.getRule().setValue(value); ++ } ++ } ++ ++ /** ++ * Returns the type hold the kind of communication it will allow or block. ++ * It MUST be filled with one of these values: jid, group or subscription. ++ * ++ * @return the type of communication it represent. ++ */ ++ public Type getType() { ++ if (this.getRule() == null) { ++ return null; ++ } else { ++ return this.getRule().getType(); ++ } ++ } ++ ++ /** ++ * Returns the element identifier to apply the action. ++ * ++ * If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID. ++ * If the type is "group", then the 'value' attribute SHOULD contain the name of a group ++ * in the user's roster. ++ * If the type is "subscription", then the 'value' attribute MUST be one of "both", "to", ++ * "from", or "none". ++ * ++ * @return the identifier to apply the action. ++ */ ++ public String getValue() { ++ if (this.getRule() == null) { ++ return null; ++ } else { ++ return this.getRule().getValue(); ++ } ++ } ++ ++ ++ /** ++ * Returns whether the receiver allows or denies every kind of communication. ++ * ++ * When filterIQ, filterMessage, filterPresence_in and filterPresence_out are not set ++ * the receiver will block all communications. ++ * ++ * @return the all communications status. ++ */ ++ public boolean isFilterEverything() { ++ return !(this.isFilterIQ() || this.isFilterMessage() || this.isFilterPresence_in() ++ || this.isFilterPresence_out()); ++ } ++ ++ ++ private PrivacyRule getRule() { ++ return rule; ++ } ++ ++ private void setRule(PrivacyRule rule) { ++ this.rule = rule; ++ } ++ /** ++ * Answer an xml representation of the receiver according to the RFC 3921. ++ * ++ * @return the text xml representation. ++ */ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ } else { ++ buf.append(">"); ++ if (this.isFilterIQ()) { ++ buf.append(""); ++ } ++ if (this.isFilterMessage()) { ++ buf.append(""); ++ } ++ if (this.isFilterPresence_in()) { ++ buf.append(""); ++ } ++ if (this.isFilterPresence_out()) { ++ buf.append(""); ++ } ++ buf.append(""); ++ } ++ return buf.toString(); ++ } ++ ++ ++ /** ++ * Privacy Rule represents the kind of action to apply. ++ * It holds the kind of communication ([jid|group|subscription]) it will allow or block and ++ * identifier to apply the action. ++ */ ++ ++ public static class PrivacyRule { ++ /** ++ * Type defines if the rule is based on JIDs, roster groups or presence subscription types. ++ * Available values are: [jid|group|subscription] ++ */ ++ private Type type; ++ /** ++ * The value hold the element identifier to apply the action. ++ * If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID. ++ * If the type is "group", then the 'value' attribute SHOULD contain the name of a group ++ * in the user's roster. ++ * If the type is "subscription", then the 'value' attribute MUST be one of "both", "to", ++ * "from", or "none". ++ */ ++ private String value; ++ ++ /** ++ * If the type is "subscription", then the 'value' attribute MUST be one of "both", ++ * "to", "from", or "none" ++ */ ++ public static final String SUBSCRIPTION_BOTH = "both"; ++ public static final String SUBSCRIPTION_TO = "to"; ++ public static final String SUBSCRIPTION_FROM = "from"; ++ public static final String SUBSCRIPTION_NONE = "none"; ++ ++ /** ++ * Returns the type constant associated with the String value. ++ */ ++ protected static PrivacyRule fromString(String value) { ++ if (value == null) { ++ return null; ++ } ++ PrivacyRule rule = new PrivacyRule(); ++ rule.setType(Type.valueOf(value.toLowerCase())); ++ return rule; ++ } ++ ++ /** ++ * Returns the type hold the kind of communication it will allow or block. ++ * It MUST be filled with one of these values: jid, group or subscription. ++ * ++ * @return the type of communication it represent. ++ */ ++ public Type getType() { ++ return type; ++ } ++ ++ /** ++ * Sets the action associated with the item, it can allow or deny the communication. ++ * ++ * @param type indicates if the receiver allows or denies the communication. ++ */ ++ private void setType(Type type) { ++ this.type = type; ++ } ++ ++ /** ++ * Returns the element identifier to apply the action. ++ * ++ * If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID. ++ * If the type is "group", then the 'value' attribute SHOULD contain the name of a group ++ * in the user's roster. ++ * If the type is "subscription", then the 'value' attribute MUST be one of "both", "to", ++ * "from", or "none". ++ * ++ * @return the identifier to apply the action. ++ */ ++ public String getValue() { ++ return value; ++ } ++ ++ /** ++ * Sets the element identifier to apply the action. ++ * ++ * If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID. ++ * If the type is "group", then the 'value' attribute SHOULD contain the name of a group ++ * in the user's roster. ++ * If the type is "subscription", then the 'value' attribute MUST be one of "both", "to", ++ * "from", or "none". ++ * ++ * @param value is the identifier to apply the action. ++ */ ++ protected void setValue(String value) { ++ if (this.isSuscription()) { ++ setSuscriptionValue(value); ++ } else { ++ this.value = value; ++ } ++ } ++ ++ /** ++ * Sets the element identifier to apply the action. ++ * ++ * The 'value' attribute MUST be one of "both", "to", "from", or "none". ++ * ++ * @param value is the identifier to apply the action. ++ */ ++ private void setSuscriptionValue(String value) { ++ String setValue; ++ if (value == null) { ++ // Do nothing ++ } ++ if (SUBSCRIPTION_BOTH.equalsIgnoreCase(value)) { ++ setValue = SUBSCRIPTION_BOTH; ++ } ++ else if (SUBSCRIPTION_TO.equalsIgnoreCase(value)) { ++ setValue = SUBSCRIPTION_TO; ++ } ++ else if (SUBSCRIPTION_FROM.equalsIgnoreCase(value)) { ++ setValue = SUBSCRIPTION_FROM; ++ } ++ else if (SUBSCRIPTION_NONE.equalsIgnoreCase(value)) { ++ setValue = SUBSCRIPTION_NONE; ++ } ++ // Default to available. ++ else { ++ setValue = null; ++ } ++ this.value = setValue; ++ } ++ ++ /** ++ * Returns if the receiver represents a subscription rule. ++ * ++ * @return if the receiver represents a subscription rule. ++ */ ++ public boolean isSuscription () { ++ return this.getType() == Type.subscription; ++ } ++ } ++ ++ /** ++ * Type defines if the rule is based on JIDs, roster groups or presence subscription types. ++ */ ++ public static enum Type { ++ /** ++ * JID being analyzed should belong to a roster group of the list's owner. ++ */ ++ group, ++ /** ++ * JID being analyzed should have a resource match, domain match or bare JID match. ++ */ ++ jid, ++ /** ++ * JID being analyzed should belong to a contact present in the owner's roster with ++ * the specified subscription status. ++ */ ++ subscription ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Registration.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Registration.java +new file mode 100644 +index 0000000..df22e27 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Registration.java +@@ -0,0 +1,155 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.ArrayList; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++ ++/** ++ * Represents registration packets. An empty GET query will cause the server to return information ++ * about it's registration support. SET queries can be used to create accounts or update ++ * existing account information. XMPP servers may require a number of attributes to be set ++ * when creating a new account. The standard account attributes are as follows: ++ *
        ++ *
      • name -- the user's name. ++ *
      • first -- the user's first name. ++ *
      • last -- the user's last name. ++ *
      • email -- the user's email address. ++ *
      • city -- the user's city. ++ *
      • state -- the user's state. ++ *
      • zip -- the user's ZIP code. ++ *
      • phone -- the user's phone number. ++ *
      • url -- the user's website. ++ *
      • date -- the date the registration took place. ++ *
      • misc -- other miscellaneous information to associate with the account. ++ *
      • text -- textual information to associate with the account. ++ *
      • remove -- empty flag to remove account. ++ *
      ++ * ++ * @author Matt Tucker ++ */ ++public class Registration extends IQ { ++ ++ private String instructions = null; ++ private Map attributes = new HashMap(); ++ private List requiredFields = new ArrayList(); ++ private boolean registered = false; ++ private boolean remove = false; ++ ++ /** ++ * Returns the registration instructions, or null if no instructions ++ * have been set. If present, instructions should be displayed to the end-user ++ * that will complete the registration process. ++ * ++ * @return the registration instructions, or null if there are none. ++ */ ++ public String getInstructions() { ++ return instructions; ++ } ++ ++ /** ++ * Sets the registration instructions. ++ * ++ * @param instructions the registration instructions. ++ */ ++ public void setInstructions(String instructions) { ++ this.instructions = instructions; ++ } ++ ++ /** ++ * Returns the map of String key/value pairs of account attributes. ++ * ++ * @return the account attributes. ++ */ ++ public Map getAttributes() { ++ return attributes; ++ } ++ ++ /** ++ * Sets the account attributes. The map must only contain String key/value pairs. ++ * ++ * @param attributes the account attributes. ++ */ ++ public void setAttributes(Map attributes) { ++ this.attributes = attributes; ++ } ++ ++ public List getRequiredFields(){ ++ return requiredFields; ++ } ++ ++ public void addAttribute(String key, String value){ ++ attributes.put(key, value); ++ } ++ ++ public void setRegistered(boolean registered){ ++ this.registered = registered; ++ } ++ ++ public boolean isRegistered(){ ++ return this.registered; ++ } ++ ++ public String getField(String key){ ++ return attributes.get(key); ++ } ++ ++ public List getFieldNames(){ ++ return new ArrayList(attributes.keySet()); ++ } ++ ++ public void setUsername(String username){ ++ attributes.put("username", username); ++ } ++ ++ public void setPassword(String password){ ++ attributes.put("password", password); ++ } ++ ++ public void setRemove(boolean remove){ ++ this.remove = remove; ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ if (instructions != null && !remove) { ++ buf.append("").append(instructions).append(""); ++ } ++ if (attributes != null && attributes.size() > 0 && !remove) { ++ for (String name : attributes.keySet()) { ++ String value = attributes.get(name); ++ buf.append("<").append(name).append(">"); ++ buf.append(value); ++ buf.append(""); ++ } ++ } ++ else if(remove){ ++ buf.append(""); ++ } ++ // Add packet extensions, if any are defined. ++ buf.append(getExtensionsXML()); ++ buf.append(""); ++ return buf.toString(); ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/RosterPacket.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/RosterPacket.java +new file mode 100644 +index 0000000..98483c8 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/RosterPacket.java +@@ -0,0 +1,311 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.util.StringUtils; ++ ++import java.util.*; ++import java.util.concurrent.CopyOnWriteArraySet; ++ ++/** ++ * Represents XMPP roster packets. ++ * ++ * @author Matt Tucker ++ */ ++public class RosterPacket extends IQ { ++ ++ private final List rosterItems = new ArrayList(); ++ /* ++ * The ver attribute following XEP-0237 ++ */ ++ private String version; ++ ++ /** ++ * Adds a roster item to the packet. ++ * ++ * @param item a roster item. ++ */ ++ public void addRosterItem(Item item) { ++ synchronized (rosterItems) { ++ rosterItems.add(item); ++ } ++ } ++ ++ public String getVersion(){ ++ return version; ++ } ++ ++ public void setVersion(String version){ ++ this.version = version; ++ } ++ ++ /** ++ * Returns the number of roster items in this roster packet. ++ * ++ * @return the number of roster items. ++ */ ++ public int getRosterItemCount() { ++ synchronized (rosterItems) { ++ return rosterItems.size(); ++ } ++ } ++ ++ /** ++ * Returns an unmodifiable collection for the roster items in the packet. ++ * ++ * @return an unmodifiable collection for the roster items in the packet. ++ */ ++ public Collection getRosterItems() { ++ synchronized (rosterItems) { ++ return Collections.unmodifiableList(new ArrayList(rosterItems)); ++ } ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ synchronized (rosterItems) { ++ for (Item entry : rosterItems) { ++ buf.append(entry.toXML()); ++ } ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ /** ++ * A roster item, which consists of a JID, their name, the type of subscription, and ++ * the groups the roster item belongs to. ++ */ ++ public static class Item { ++ ++ private String user; ++ private String name; ++ private ItemType itemType; ++ private ItemStatus itemStatus; ++ private final Set groupNames; ++ ++ /** ++ * Creates a new roster item. ++ * ++ * @param user the user. ++ * @param name the user's name. ++ */ ++ public Item(String user, String name) { ++ this.user = user.toLowerCase(); ++ this.name = name; ++ itemType = null; ++ itemStatus = null; ++ groupNames = new CopyOnWriteArraySet(); ++ } ++ ++ /** ++ * Returns the user. ++ * ++ * @return the user. ++ */ ++ public String getUser() { ++ return user; ++ } ++ ++ /** ++ * Returns the user's name. ++ * ++ * @return the user's name. ++ */ ++ public String getName() { ++ return name; ++ } ++ ++ /** ++ * Sets the user's name. ++ * ++ * @param name the user's name. ++ */ ++ public void setName(String name) { ++ this.name = name; ++ } ++ ++ /** ++ * Returns the roster item type. ++ * ++ * @return the roster item type. ++ */ ++ public ItemType getItemType() { ++ return itemType; ++ } ++ ++ /** ++ * Sets the roster item type. ++ * ++ * @param itemType the roster item type. ++ */ ++ public void setItemType(ItemType itemType) { ++ this.itemType = itemType; ++ } ++ ++ /** ++ * Returns the roster item status. ++ * ++ * @return the roster item status. ++ */ ++ public ItemStatus getItemStatus() { ++ return itemStatus; ++ } ++ ++ /** ++ * Sets the roster item status. ++ * ++ * @param itemStatus the roster item status. ++ */ ++ public void setItemStatus(ItemStatus itemStatus) { ++ this.itemStatus = itemStatus; ++ } ++ ++ /** ++ * Returns an unmodifiable set of the group names that the roster item ++ * belongs to. ++ * ++ * @return an unmodifiable set of the group names. ++ */ ++ public Set getGroupNames() { ++ return Collections.unmodifiableSet(groupNames); ++ } ++ ++ /** ++ * Adds a group name. ++ * ++ * @param groupName the group name. ++ */ ++ public void addGroupName(String groupName) { ++ groupNames.add(groupName); ++ } ++ ++ /** ++ * Removes a group name. ++ * ++ * @param groupName the group name. ++ */ ++ public void removeGroupName(String groupName) { ++ groupNames.remove(groupName); ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ for (String groupName : groupNames) { ++ buf.append("").append(StringUtils.escapeForXML(groupName)).append(""); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ } ++ ++ /** ++ * The subscription status of a roster item. An optional element that indicates ++ * the subscription status if a change request is pending. ++ */ ++ public static class ItemStatus { ++ ++ /** ++ * Request to subcribe. ++ */ ++ public static final ItemStatus SUBSCRIPTION_PENDING = new ItemStatus("subscribe"); ++ ++ /** ++ * Request to unsubscribe. ++ */ ++ public static final ItemStatus UNSUBSCRIPTION_PENDING = new ItemStatus("unsubscribe"); ++ ++ public static ItemStatus fromString(String value) { ++ if (value == null) { ++ return null; ++ } ++ value = value.toLowerCase(); ++ if ("unsubscribe".equals(value)) { ++ return UNSUBSCRIPTION_PENDING; ++ } ++ else if ("subscribe".equals(value)) { ++ return SUBSCRIPTION_PENDING; ++ } ++ else { ++ return null; ++ } ++ } ++ ++ private String value; ++ ++ /** ++ * Returns the item status associated with the specified string. ++ * ++ * @param value the item status. ++ */ ++ private ItemStatus(String value) { ++ this.value = value; ++ } ++ ++ public String toString() { ++ return value; ++ } ++ } ++ ++ public static enum ItemType { ++ ++ /** ++ * The user and subscriber have no interest in each other's presence. ++ */ ++ none, ++ ++ /** ++ * The user is interested in receiving presence updates from the subscriber. ++ */ ++ to, ++ ++ /** ++ * The subscriber is interested in receiving presence updates from the user. ++ */ ++ from, ++ ++ /** ++ * The user and subscriber have a mutual interest in each other's presence. ++ */ ++ both, ++ ++ /** ++ * The user wishes to stop receiving presence updates from the subscriber. ++ */ ++ remove ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Session.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Session.java +new file mode 100644 +index 0000000..d034666 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Session.java +@@ -0,0 +1,45 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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; ++ ++/** ++ * IQ packet that will be sent to the server to establish a session.

      ++ * ++ * If a server supports sessions, it MUST include a session element in the ++ * stream features it advertises to a client after the completion of stream authentication. ++ * Upon being informed that session establishment is required by the server the client MUST ++ * establish a session if it desires to engage in instant messaging and presence functionality.

      ++ * ++ * For more information refer to the following ++ * link. ++ * ++ * @author Gaston Dombiak ++ */ ++public class Session extends IQ { ++ ++ public Session() { ++ setType(IQ.Type.SET); ++ } ++ ++ public String getChildElementXML() { ++ return ""; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/StreamError.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/StreamError.java +new file mode 100644 +index 0000000..8bb4c75 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/StreamError.java +@@ -0,0 +1,106 @@ ++/** ++ * $Revision: 2408 $ ++ * $Date: 2004-11-02 20:53:30 -0300 (Tue, 02 Nov 2004) $ ++ * ++ * Copyright 2003-2005 Jive Software. ++ * ++ * All rights reserved. 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; ++ ++/** ++ * Represents a stream error packet. Stream errors are unrecoverable errors where the server ++ * will close the unrelying TCP connection after the stream error was sent to the client. ++ * These is the list of stream errors as defined in the XMPP spec:

      ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ *
      CodeDescription
      bad-format the entity has sent XML that cannot be processed
      unsupported-encoding the entity has sent a namespace prefix that is ++ * unsupported
      bad-namespace-prefix Remote Server Timeout
      conflict the server is closing the active stream for this entity ++ * because a new stream has been initiated that conflicts with the existing ++ * stream.
      connection-timeout the entity has not generated any traffic over ++ * the stream for some period of time.
      host-gone the value of the 'to' attribute provided by the initiating ++ * entity in the stream header corresponds to a hostname that is no longer hosted by ++ * the server.
      host-unknown the value of the 'to' attribute provided by the ++ * initiating entity in the stream header does not correspond to a hostname that is ++ * hosted by the server.
      improper-addressing a stanza sent between two servers lacks a 'to' ++ * or 'from' attribute
      internal-server-error the server has experienced a ++ * misconfiguration.
      invalid-from the JID or hostname provided in a 'from' address does ++ * not match an authorized JID.
      invalid-id the stream ID or dialback ID is invalid or does not match ++ * an ID previously provided.
      invalid-namespace the streams namespace name is invalid.
      invalid-xml the entity has sent invalid XML over the stream.
      not-authorized the entity has attempted to send data before the ++ * stream has been authenticated
      policy-violation the entity has violated some local service ++ * policy.
      remote-connection-failed Rthe server is unable to properly connect ++ * to a remote entity.
      resource-constraint Rthe server lacks the system resources necessary ++ * to service the stream.
      restricted-xml the entity has attempted to send restricted XML ++ * features.
      see-other-host the server will not provide service to the initiating ++ * entity but is redirecting traffic to another host.
      system-shutdown the server is being shut down and all active streams ++ * are being closed.
      undefined-condition the error condition is not one of those defined ++ * by the other conditions in this list.
      unsupported-encoding the initiating entity has encoded the stream in ++ * an encoding that is not supported.
      unsupported-stanza-type the initiating entity has sent a first-level ++ * child of the stream that is not supported.
      unsupported-version the value of the 'version' attribute provided by ++ * the initiating entity in the stream header specifies a version of XMPP that is not ++ * supported.
      xml-not-well-formed the initiating entity has sent XML that is ++ * not well-formed.
      ++ * ++ * @author Gaston Dombiak ++ */ ++public class StreamError { ++ ++ private String code; ++ ++ public StreamError(String code) { ++ super(); ++ this.code = code; ++ } ++ ++ /** ++ * Returns the error code. ++ * ++ * @return the error code. ++ */ ++ public String getCode() { ++ return code; ++ } ++ ++ public String toString() { ++ StringBuilder txt = new StringBuilder(); ++ txt.append("stream:error (").append(code).append(")"); ++ return txt.toString(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/UnknownPacket.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/UnknownPacket.java +new file mode 100644 +index 0000000..da65be8 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/UnknownPacket.java +@@ -0,0 +1,34 @@ ++/** ++ * Copyright 2012 Miron Cuperman. ++ * ++ * All rights reserved. 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; ++ ++ ++ ++/** ++ * Contains packets that are unknown to Smack, but may be processed by extensions. ++ */ ++public abstract class UnknownPacket extends Packet implements PacketExtension { ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()); ++ if (getXmlns() != null) { ++ buf.append(" xmlns=\"").append(getNamespace()).append("\""); ++ } ++ buf.append("/>"); ++ return buf.toString(); ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/XMPPError.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/XMPPError.java +new file mode 100644 +index 0000000..770a09c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/XMPPError.java +@@ -0,0 +1,453 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.*; ++ ++/** ++ * Represents a XMPP error sub-packet. Typically, a server responds to a request that has ++ * problems by sending the packet back and including an error packet. Each error has a code, type, ++ * error condition as well as as an optional text explanation. Typical errors are:

      ++ * ++ * ++ *
      ++ * ++ * ++ * > ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ *
      CodeXMPP ErrorType
      500interna-server-errorWAIT
      403forbiddenAUTH
      400bad-requestMODIFY
      404item-not-foundCANCEL
      409conflictCANCEL
      501feature-not-implementedCANCEL
      302goneMODIFY
      400jid-malformedMODIFY
      406no-acceptable MODIFY
      405not-allowedCANCEL
      401not-authorizedAUTH
      402payment-requiredAUTH
      404recipient-unavailableWAIT
      302redirectMODIFY
      407registration-requiredAUTH
      404remote-server-not-foundCANCEL
      504remote-server-timeoutWAIT
      502remote-server-errorCANCEL
      500resource-constraintWAIT
      503service-unavailableCANCEL
      407subscription-requiredAUTH
      500undefined-conditionWAIT
      400unexpected-conditionWAIT
      408request-timeoutCANCEL
      ++ * ++ * @author Matt Tucker ++ */ ++public class XMPPError { ++ ++ private int code; ++ private Type type; ++ private String condition; ++ private String message; ++ private List applicationExtensions = null; ++ ++ ++ /** ++ * Creates a new error with the specified condition infering the type and code. ++ * If the Condition is predefined, client code should be like: ++ * new XMPPError(XMPPError.Condition.remote_server_timeout); ++ * If the Condition is not predefined, invocations should be like ++ * new XMPPError(new XMPPError.Condition("my_own_error")); ++ * ++ * @param condition the error condition. ++ */ ++ public XMPPError(Condition condition) { ++ this.init(condition); ++ this.message = null; ++ } ++ ++ /** ++ * Creates a new error with the specified condition and message infering the type and code. ++ * If the Condition is predefined, client code should be like: ++ * new XMPPError(XMPPError.Condition.remote_server_timeout, "Error Explanation"); ++ * If the Condition is not predefined, invocations should be like ++ * new XMPPError(new XMPPError.Condition("my_own_error"), "Error Explanation"); ++ * ++ * @param condition the error condition. ++ * @param messageText a message describing the error. ++ */ ++ public XMPPError(Condition condition, String messageText) { ++ this.init(condition); ++ this.message = messageText; ++ } ++ ++ /** ++ * Creates a new error with the specified code and no message. ++ * ++ * @param code the error code. ++ * @deprecated new errors should be created using the constructor XMPPError(condition) ++ */ ++ public XMPPError(int code) { ++ this.code = code; ++ this.message = null; ++ } ++ ++ /** ++ * Creates a new error with the specified code and message. ++ * deprecated ++ * ++ * @param code the error code. ++ * @param message a message describing the error. ++ * @deprecated new errors should be created using the constructor XMPPError(condition, message) ++ */ ++ public XMPPError(int code, String message) { ++ this.code = code; ++ this.message = message; ++ } ++ ++ /** ++ * Creates a new error with the specified code, type, condition and message. ++ * This constructor is used when the condition is not recognized automatically by XMPPError ++ * i.e. there is not a defined instance of ErrorCondition or it does not applies the default ++ * specification. ++ * ++ * @param code the error code. ++ * @param type the error type. ++ * @param condition the error condition. ++ * @param message a message describing the error. ++ * @param extension list of packet extensions ++ */ ++ public XMPPError(int code, Type type, String condition, String message, ++ List extension) { ++ this.code = code; ++ this.type = type; ++ this.condition = condition; ++ this.message = message; ++ this.applicationExtensions = extension; ++ } ++ ++ /** ++ * Initialize the error infering the type and code for the received condition. ++ * ++ * @param condition the error condition. ++ */ ++ private void init(Condition condition) { ++ // Look for the condition and its default code and type ++ ErrorSpecification defaultErrorSpecification = ErrorSpecification.specFor(condition); ++ this.condition = condition.value; ++ if (defaultErrorSpecification != null) { ++ // If there is a default error specification for the received condition, ++ // it get configured with the infered type and code. ++ this.type = defaultErrorSpecification.getType(); ++ this.code = defaultErrorSpecification.getCode(); ++ } ++ } ++ /** ++ * Returns the error condition. ++ * ++ * @return the error condition. ++ */ ++ public String getCondition() { ++ return condition; ++ } ++ ++ /** ++ * Returns the error type. ++ * ++ * @return the error type. ++ */ ++ public Type getType() { ++ return type; ++ } ++ ++ /** ++ * Returns the error code. ++ * ++ * @return the error code. ++ */ ++ public int getCode() { ++ return code; ++ } ++ ++ /** ++ * Returns the message describing the error, or null if there is no message. ++ * ++ * @return the message describing the error, or null if there is no message. ++ */ ++ public String getMessage() { ++ return message; ++ } ++ ++ /** ++ * Returns the error as XML. ++ * ++ * @return the error as XML. ++ */ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ if (condition != null) { ++ buf.append("<").append(condition); ++ buf.append(" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"); ++ } ++ if (message != null) { ++ buf.append(""); ++ buf.append(message); ++ buf.append(""); ++ } ++ for (PacketExtension element : this.getExtensions()) { ++ buf.append(element.toXML()); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ public String toString() { ++ StringBuilder txt = new StringBuilder(); ++ if (condition != null) { ++ txt.append(condition); ++ } ++ txt.append("(").append(code).append(")"); ++ if (message != null) { ++ txt.append(" ").append(message); ++ } ++ return txt.toString(); ++ } ++ ++ /** ++ * Returns an Iterator for the error extensions attached to the xmppError. ++ * An application MAY provide application-specific error information by including a ++ * properly-namespaced child in the error element. ++ * ++ * @return an Iterator for the error extensions. ++ */ ++ public synchronized List getExtensions() { ++ if (applicationExtensions == null) { ++ return Collections.emptyList(); ++ } ++ return Collections.unmodifiableList(applicationExtensions); ++ } ++ ++ /** ++ * Returns the first patcket extension that matches the specified element name and ++ * namespace, or null if it doesn't exist. ++ * ++ * @param elementName the XML element name of the packet extension. ++ * @param namespace the XML element namespace of the packet extension. ++ * @return the extension, or null if it doesn't exist. ++ */ ++ public synchronized PacketExtension getExtension(String elementName, String namespace) { ++ if (applicationExtensions == null || elementName == null || namespace == null) { ++ return null; ++ } ++ for (PacketExtension ext : applicationExtensions) { ++ if (elementName.equals(ext.getElementName()) && namespace.equals(ext.getNamespace())) { ++ return ext; ++ } ++ } ++ return null; ++ } ++ ++ /** ++ * Adds a packet extension to the error. ++ * ++ * @param extension a packet extension. ++ */ ++ public synchronized void addExtension(PacketExtension extension) { ++ if (applicationExtensions == null) { ++ applicationExtensions = new ArrayList(); ++ } ++ applicationExtensions.add(extension); ++ } ++ ++ /** ++ * Set the packet extension to the error. ++ * ++ * @param extension a packet extension. ++ */ ++ public synchronized void setExtension(List extension) { ++ applicationExtensions = extension; ++ } ++ ++ /** ++ * A class to represent the type of the Error. The types are: ++ * ++ *

        ++ *
      • XMPPError.Type.WAIT - retry after waiting (the error is temporary) ++ *
      • XMPPError.Type.CANCEL - do not retry (the error is unrecoverable) ++ *
      • XMPPError.Type.MODIFY - retry after changing the data sent ++ *
      • XMPPError.Type.AUTH - retry after providing credentials ++ *
      • XMPPError.Type.CONTINUE - proceed (the condition was only a warning) ++ *
      ++ */ ++ public static enum Type { ++ WAIT, ++ CANCEL, ++ MODIFY, ++ AUTH, ++ CONTINUE ++ } ++ ++ /** ++ * A class to represent predefined error conditions. ++ */ ++ public static class Condition { ++ ++ public static final Condition interna_server_error = new Condition("internal-server-error"); ++ public static final Condition forbidden = new Condition("forbidden"); ++ public static final Condition bad_request = new Condition("bad-request"); ++ public static final Condition conflict = new Condition("conflict"); ++ public static final Condition feature_not_implemented = new Condition("feature-not-implemented"); ++ public static final Condition gone = new Condition("gone"); ++ public static final Condition item_not_found = new Condition("item-not-found"); ++ public static final Condition jid_malformed = new Condition("jid-malformed"); ++ public static final Condition no_acceptable = new Condition("not-acceptable"); ++ public static final Condition not_allowed = new Condition("not-allowed"); ++ public static final Condition not_authorized = new Condition("not-authorized"); ++ public static final Condition payment_required = new Condition("payment-required"); ++ public static final Condition recipient_unavailable = new Condition("recipient-unavailable"); ++ public static final Condition redirect = new Condition("redirect"); ++ public static final Condition registration_required = new Condition("registration-required"); ++ public static final Condition remote_server_error = new Condition("remote-server-error"); ++ public static final Condition remote_server_not_found = new Condition("remote-server-not-found"); ++ public static final Condition remote_server_timeout = new Condition("remote-server-timeout"); ++ public static final Condition resource_constraint = new Condition("resource-constraint"); ++ public static final Condition service_unavailable = new Condition("service-unavailable"); ++ public static final Condition subscription_required = new Condition("subscription-required"); ++ public static final Condition undefined_condition = new Condition("undefined-condition"); ++ public static final Condition unexpected_request = new Condition("unexpected-request"); ++ public static final Condition request_timeout = new Condition("request-timeout"); ++ ++ private String value; ++ ++ public Condition(String value) { ++ this.value = value; ++ } ++ ++ public String toString() { ++ return value; ++ } ++ } ++ ++ ++ /** ++ * A class to represent the error specification used to infer common usage. ++ */ ++ private static class ErrorSpecification { ++ private int code; ++ private Type type; ++ private Condition condition; ++ private static Map instances = errorSpecifications(); ++ ++ private ErrorSpecification(Condition condition, Type type, int code) { ++ this.code = code; ++ this.type = type; ++ this.condition = condition; ++ } ++ ++ private static Map errorSpecifications() { ++ Map instances = new HashMap(22); ++ instances.put(Condition.interna_server_error, new ErrorSpecification( ++ Condition.interna_server_error, Type.WAIT, 500)); ++ instances.put(Condition.forbidden, new ErrorSpecification(Condition.forbidden, ++ Type.AUTH, 403)); ++ instances.put(Condition.bad_request, new XMPPError.ErrorSpecification( ++ Condition.bad_request, Type.MODIFY, 400)); ++ instances.put(Condition.item_not_found, new XMPPError.ErrorSpecification( ++ Condition.item_not_found, Type.CANCEL, 404)); ++ instances.put(Condition.conflict, new XMPPError.ErrorSpecification( ++ Condition.conflict, Type.CANCEL, 409)); ++ instances.put(Condition.feature_not_implemented, new XMPPError.ErrorSpecification( ++ Condition.feature_not_implemented, Type.CANCEL, 501)); ++ instances.put(Condition.gone, new XMPPError.ErrorSpecification( ++ Condition.gone, Type.MODIFY, 302)); ++ instances.put(Condition.jid_malformed, new XMPPError.ErrorSpecification( ++ Condition.jid_malformed, Type.MODIFY, 400)); ++ instances.put(Condition.no_acceptable, new XMPPError.ErrorSpecification( ++ Condition.no_acceptable, Type.MODIFY, 406)); ++ instances.put(Condition.not_allowed, new XMPPError.ErrorSpecification( ++ Condition.not_allowed, Type.CANCEL, 405)); ++ instances.put(Condition.not_authorized, new XMPPError.ErrorSpecification( ++ Condition.not_authorized, Type.AUTH, 401)); ++ instances.put(Condition.payment_required, new XMPPError.ErrorSpecification( ++ Condition.payment_required, Type.AUTH, 402)); ++ instances.put(Condition.recipient_unavailable, new XMPPError.ErrorSpecification( ++ Condition.recipient_unavailable, Type.WAIT, 404)); ++ instances.put(Condition.redirect, new XMPPError.ErrorSpecification( ++ Condition.redirect, Type.MODIFY, 302)); ++ instances.put(Condition.registration_required, new XMPPError.ErrorSpecification( ++ Condition.registration_required, Type.AUTH, 407)); ++ instances.put(Condition.remote_server_not_found, new XMPPError.ErrorSpecification( ++ Condition.remote_server_not_found, Type.CANCEL, 404)); ++ instances.put(Condition.remote_server_timeout, new XMPPError.ErrorSpecification( ++ Condition.remote_server_timeout, Type.WAIT, 504)); ++ instances.put(Condition.remote_server_error, new XMPPError.ErrorSpecification( ++ Condition.remote_server_error, Type.CANCEL, 502)); ++ instances.put(Condition.resource_constraint, new XMPPError.ErrorSpecification( ++ Condition.resource_constraint, Type.WAIT, 500)); ++ instances.put(Condition.service_unavailable, new XMPPError.ErrorSpecification( ++ Condition.service_unavailable, Type.CANCEL, 503)); ++ instances.put(Condition.subscription_required, new XMPPError.ErrorSpecification( ++ Condition.subscription_required, Type.AUTH, 407)); ++ instances.put(Condition.undefined_condition, new XMPPError.ErrorSpecification( ++ Condition.undefined_condition, Type.WAIT, 500)); ++ instances.put(Condition.unexpected_request, new XMPPError.ErrorSpecification( ++ Condition.unexpected_request, Type.WAIT, 400)); ++ instances.put(Condition.request_timeout, new XMPPError.ErrorSpecification( ++ Condition.request_timeout, Type.CANCEL, 408)); ++ ++ return instances; ++ } ++ ++ protected static ErrorSpecification specFor(Condition condition) { ++ return instances.get(condition); ++ } ++ ++ /** ++ * Returns the error condition. ++ * ++ * @return the error condition. ++ */ ++ protected Condition getCondition() { ++ return condition; ++ } ++ ++ /** ++ * Returns the error type. ++ * ++ * @return the error type. ++ */ ++ protected Type getType() { ++ return type; ++ } ++ ++ /** ++ * Returns the error code. ++ * ++ * @return the error code. ++ */ ++ protected int getCode() { ++ return code; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/package.html +new file mode 100644 +index 0000000..18a6405 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/package.html +@@ -0,0 +1 @@ ++XML packets that are part of the XMPP protocol. +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/EmbeddedExtensionProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/EmbeddedExtensionProvider.java +new file mode 100644 +index 0000000..e7b4b93 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/EmbeddedExtensionProvider.java +@@ -0,0 +1,109 @@ ++/** ++ * All rights reserved. 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.util.ArrayList; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.jivesoftware.smack.util.PacketParserUtils; ++import org.jivesoftware.smackx.pubsub.provider.ItemProvider; ++import org.jivesoftware.smackx.pubsub.provider.ItemsProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * ++ * This class simplifies parsing of embedded elements by using the ++ * Template Method Pattern. ++ * After extracting the current element attributes and content of any child elements, the template method ++ * ({@link #createReturnExtension(String, String, Map, List)} is called. Subclasses ++ * then override this method to create the specific return type. ++ * ++ *

      To use this class, you simply register your subclasses as extension providers in the ++ * smack.properties file. Then they will be automatically picked up and used to parse ++ * any child elements. ++ * ++ *

      ++ * For example, given the following message
      ++ * 
      ++ * <message from='pubsub.shakespeare.lit' to='francisco@denmark.lit' id='foo>
      ++ *    <event xmlns='http://jabber.org/protocol/pubsub#event>
      ++ *       <items node='princely_musings'>
      ++ *          <item id='asdjkwei3i34234n356'>
      ++ *             <entry xmlns='http://www.w3.org/2005/Atom'>
      ++ *                <title>Soliloquy</title>
      ++ *                <link rel='alternative' type='text/html'/>
      ++ *                <id>tag:denmark.lit,2003:entry-32397</id>
      ++ *             </entry>
      ++ *          </item>
      ++ *       </items>
      ++ *    </event>
      ++ * </message>
      ++ * 
      ++ * I would have a classes
      ++ * {@link ItemsProvider} extends {@link EmbeddedExtensionProvider}
      ++ * {@link ItemProvider} extends {@link EmbeddedExtensionProvider}
      ++ * and
      ++ * AtomProvider extends {@link PacketExtensionProvider}
      ++ * 
      ++ * These classes are then registered in the meta-inf/smack.providers file
      ++ * as follows.
      ++ * 
      ++ *   <extensionProvider>
      ++ *      <elementName>items</elementName>
      ++ *      <namespace>http://jabber.org/protocol/pubsub#event</namespace>
      ++ *      <className>org.jivesoftware.smackx.provider.ItemsEventProvider</className>
      ++ *   </extensionProvider>
      ++ *   <extensionProvider>
      ++ *       <elementName>item</elementName>
      ++ *       <namespace>http://jabber.org/protocol/pubsub#event</namespace>
      ++ *       <className>org.jivesoftware.smackx.provider.ItemProvider</className>
      ++ *   </extensionProvider>
      ++ * 
      ++ * 
      ++ * ++ * @author Robin Collier ++ */ ++abstract public class EmbeddedExtensionProvider implements PacketExtensionProvider ++{ ++ ++ final public PacketExtension parseExtension(XmlPullParser parser) throws Exception ++ { ++ String namespace = parser.getNamespace(); ++ String name = parser.getName(); ++ Map attMap = new HashMap(); ++ ++ for(int i=0; i extensions = new ArrayList(); ++ ++ do ++ { ++ int tag = parser.next(); ++ ++ if (tag == XmlPullParser.START_TAG) ++ extensions.add(PacketParserUtils.parsePacketExtension(parser.getName(), parser.getNamespace(), parser)); ++ } while (!name.equals(parser.getName())); ++ ++ return createReturnExtension(name, namespace, attMap, extensions); ++ } ++ ++ abstract protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/IQProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/IQProvider.java +new file mode 100644 +index 0000000..936c6be +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/IQProvider.java +@@ -0,0 +1,47 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.packet.IQ; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * An interface for parsing custom IQ packets. Each IQProvider must be registered with ++ * the ProviderManager class for it to be used. Every implementation of this ++ * interface must have a public, no-argument constructor. ++ * ++ * @author Matt Tucker ++ */ ++public interface IQProvider { ++ ++ /** ++ * Parse the IQ sub-document and create an IQ instance. Each IQ must have a ++ * single child element. At the beginning of the method call, the xml parser ++ * will be positioned at the opening tag of the IQ child element. At the end ++ * of the method call, the parser must be positioned on the closing tag ++ * of the child element. ++ * ++ * @param parser an XML parser. ++ * @return a new IQ instance. ++ * @throws Exception if an error occurs parsing the XML. ++ */ ++ public IQ parseIQ(XmlPullParser parser) throws Exception; ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/PacketExtensionProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/PacketExtensionProvider.java +new file mode 100644 +index 0000000..8fc0af3 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/PacketExtensionProvider.java +@@ -0,0 +1,46 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.packet.PacketExtension; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * An interface for parsing custom packets extensions. Each PacketExtensionProvider must ++ * be registered with the ProviderManager class for it to be used. Every implementation ++ * of this interface must have a public, no-argument constructor. ++ * ++ * @author Matt Tucker ++ */ ++public interface PacketExtensionProvider { ++ ++ /** ++ * Parse an extension sub-packet and create a PacketExtension instance. At ++ * the beginning of the method call, the xml parser will be positioned on the ++ * opening element of the packet extension. At the end of the method call, the ++ * parser must be positioned on the closing element of the packet extension. ++ * ++ * @param parser an XML parser. ++ * @return a new IQ instance. ++ * @throws java.lang.Exception if an error occurs parsing the XML. ++ */ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception; ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/PrivacyProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/PrivacyProvider.java +new file mode 100644 +index 0000000..a995399 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/PrivacyProvider.java +@@ -0,0 +1,134 @@ ++package org.jivesoftware.smack.provider; ++ ++import org.jivesoftware.smack.packet.DefaultPacketExtension; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Privacy; ++import org.jivesoftware.smack.packet.PrivacyItem; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.util.ArrayList; ++ ++/** ++ * The PrivacyProvider parses {@link Privacy} packets. {@link Privacy} ++ * Parses the query sub-document and creates an instance of {@link Privacy}. ++ * For each item in the list element, it creates an instance ++ * of {@link PrivacyItem} and {@link org.jivesoftware.smack.packet.PrivacyItem.PrivacyRule}. ++ * ++ * @author Francisco Vives ++ */ ++public class PrivacyProvider implements IQProvider { ++ ++ public PrivacyProvider() { ++ } ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ Privacy privacy = new Privacy(); ++ /* privacy.addExtension(PacketParserUtils.parsePacketExtension(parser ++ .getName(), parser.getNamespace(), parser)); */ ++ privacy.addExtension(new DefaultPacketExtension(parser.getName(), parser.getNamespace())); ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("active")) { ++ String activeName = parser.getAttributeValue("", "name"); ++ if (activeName == null) { ++ privacy.setDeclineActiveList(true); ++ } else { ++ privacy.setActiveName(activeName); ++ } ++ } ++ else if (parser.getName().equals("default")) { ++ String defaultName = parser.getAttributeValue("", "name"); ++ if (defaultName == null) { ++ privacy.setDeclineDefaultList(true); ++ } else { ++ privacy.setDefaultName(defaultName); ++ } ++ } ++ else if (parser.getName().equals("list")) { ++ parseList(parser, privacy); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("query")) { ++ done = true; ++ } ++ } ++ } ++ ++ return privacy; ++ } ++ ++ // Parse the list complex type ++ public void parseList(XmlPullParser parser, Privacy privacy) throws Exception { ++ boolean done = false; ++ String listName = parser.getAttributeValue("", "name"); ++ ArrayList items = new ArrayList(); ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("item")) { ++ items.add(parseItem(parser)); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("list")) { ++ done = true; ++ } ++ } ++ } ++ ++ privacy.setPrivacyList(listName, items); ++ } ++ ++ // Parse the list complex type ++ public PrivacyItem parseItem(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ // Retrieves the required attributes ++ String actionValue = parser.getAttributeValue("", "action"); ++ String orderValue = parser.getAttributeValue("", "order"); ++ String type = parser.getAttributeValue("", "type"); ++ ++ /* ++ * According the action value it sets the allow status. The fall-through action is assumed ++ * to be "allow" ++ */ ++ boolean allow = true; ++ if ("allow".equalsIgnoreCase(actionValue)) { ++ allow = true; ++ } else if ("deny".equalsIgnoreCase(actionValue)) { ++ allow = false; ++ } ++ // Set the order number ++ int order = Integer.parseInt(orderValue); ++ ++ // Create the privacy item ++ PrivacyItem item = new PrivacyItem(type, allow, order); ++ item.setValue(parser.getAttributeValue("", "value")); ++ ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("iq")) { ++ item.setFilterIQ(true); ++ } ++ if (parser.getName().equals("message")) { ++ item.setFilterMessage(true); ++ } ++ if (parser.getName().equals("presence-in")) { ++ item.setFilterPresence_in(true); ++ } ++ if (parser.getName().equals("presence-out")) { ++ item.setFilterPresence_out(true); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("item")) { ++ done = true; ++ } ++ } ++ } ++ return item; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/ProviderManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/ProviderManager.java +new file mode 100644 +index 0000000..a76850f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/ProviderManager.java +@@ -0,0 +1,451 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.xmlpull.v1.XmlPullParserFactory; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.io.InputStream; ++import java.net.URL; ++import java.util.*; ++import java.util.concurrent.ConcurrentHashMap; ++ ++/** ++ * Manages providers for parsing custom XML sub-documents of XMPP packets. Two types of ++ * providers exist:
        ++ *
      • IQProvider -- parses IQ requests into Java objects. ++ *
      • PacketExtension -- parses XML sub-documents attached to packets into ++ * PacketExtension instances.
      ++ * ++ * IQProvider

      ++ * ++ * By default, Smack only knows how to process IQ packets with sub-packets that ++ * are in a few namespaces such as:

        ++ *
      • jabber:iq:auth ++ *
      • jabber:iq:roster ++ *
      • jabber:iq:register
      ++ * ++ * Because many more IQ types are part of XMPP and its extensions, a pluggable IQ parsing ++ * mechanism is provided. IQ providers are registered programatically or by creating a ++ * smack.providers file in the META-INF directory of your JAR file. The file is an XML ++ * document that contains one or more iqProvider entries, as in the following example: ++ * ++ *
      ++ * <?xml version="1.0"?>
      ++ * <smackProviders>
      ++ *     <iqProvider>
      ++ *         <elementName>query</elementName>
      ++ *         <namespace>jabber:iq:time</namespace>
      ++ *         <className>org.jivesoftware.smack.packet.Time</className>
      ++ *     </iqProvider>
      ++ * </smackProviders>
      ++ * ++ * Each IQ provider is associated with an element name and a namespace. If multiple provider ++ * entries attempt to register to handle the same namespace, the first entry loaded from the ++ * classpath will take precedence. The IQ provider class can either implement the IQProvider ++ * interface, or extend the IQ class. In the former case, each IQProvider is responsible for ++ * parsing the raw XML stream to create an IQ instance. In the latter case, bean introspection ++ * is used to try to automatically set properties of the IQ instance using the values found ++ * in the IQ packet XML. For example, an XMPP time packet resembles the following: ++ *
      ++ * <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'>
      ++ *     <query xmlns='jabber:iq:time'>
      ++ *         <utc>20020910T17:58:35</utc>
      ++ *         <tz>MDT</tz>
      ++ *         <display>Tue Sep 10 12:58:35 2002</display>
      ++ *     </query>
      ++ * </iq>
      ++ * ++ * In order for this packet to be automatically mapped to the Time object listed in the ++ * providers file above, it must have the methods setUtc(String), setTz(String), and ++ * setDisplay(String). The introspection service will automatically try to convert the String ++ * value from the XML into a boolean, int, long, float, double, or Class depending on the ++ * type the IQ instance expects.

      ++ * ++ * A pluggable system for packet extensions, child elements in a custom namespace for ++ * message and presence packets, also exists. Each extension provider ++ * is registered with a name space in the smack.providers file as in the following example: ++ * ++ *

      ++ * <?xml version="1.0"?>
      ++ * <smackProviders>
      ++ *     <extensionProvider>
      ++ *         <elementName>x</elementName>
      ++ *         <namespace>jabber:iq:event</namespace>
      ++ *         <className>org.jivesoftware.smack.packet.MessageEvent</className>
      ++ *     </extensionProvider>
      ++ * </smackProviders>
      ++ * ++ * If multiple provider entries attempt to register to handle the same element name and namespace, ++ * the first entry loaded from the classpath will take precedence. Whenever a packet extension ++ * is found in a packet, parsing will be passed to the correct provider. Each provider ++ * can either implement the PacketExtensionProvider interface or be a standard Java Bean. In ++ * the former case, each extension provider is responsible for parsing the raw XML stream to ++ * contruct an object. In the latter case, bean introspection is used to try to automatically ++ * set the properties of the class using the values in the packet extension sub-element. When an ++ * extension provider is not registered for an element name and namespace combination, Smack will ++ * store all top-level elements of the sub-packet in DefaultPacketExtension object and then ++ * attach it to the packet.

      ++ * ++ * It is possible to provide a custom provider manager instead of the default implementation ++ * provided by Smack. If you want to provide your own provider manager then you need to do it ++ * before creating any {@link org.jivesoftware.smack.Connection} by sending the static ++ * {@link #setInstance(ProviderManager)} message. Trying to change the provider manager after ++ * an Connection was created will result in an {@link IllegalStateException} error. ++ * ++ * @author Matt Tucker ++ */ ++public class ProviderManager { ++ ++ private static ProviderManager instance; ++ ++ private Map extensionProviders = new ConcurrentHashMap(); ++ private Map iqProviders = new ConcurrentHashMap(); ++ ++ /** ++ * Returns the only ProviderManager valid instance. Use {@link #setInstance(ProviderManager)} ++ * to configure your own provider manager. If non was provided then an instance of this ++ * class will be used. ++ * ++ * @return the only ProviderManager valid instance. ++ */ ++ public static synchronized ProviderManager getInstance() { ++ if (instance == null) { ++ instance = new ProviderManager(); ++ } ++ return instance; ++ } ++ ++ /** ++ * Sets the only ProviderManager valid instance to be used by all Connections. If you ++ * want to provide your own provider manager then you need to do it before creating ++ * any Connection. Otherwise an IllegalStateException will be thrown. ++ * ++ * @param providerManager the only ProviderManager valid instance to be used. ++ * @throws IllegalStateException if a provider manager was already configued. ++ */ ++ public static synchronized void setInstance(ProviderManager providerManager) { ++ if (instance != null) { ++ throw new IllegalStateException("ProviderManager singleton already set"); ++ } ++ instance = providerManager; ++ } ++ ++ protected void initialize() { ++ // Load IQ processing providers. ++ try { ++ // Get an array of class loaders to try loading the providers files from. ++ ClassLoader[] classLoaders = getClassLoaders(); ++ for (ClassLoader classLoader : classLoaders) { ++ Enumeration providerEnum = classLoader.getResources( ++ "META-INF/smack.providers"); ++ while (providerEnum.hasMoreElements()) { ++ URL url = (URL) providerEnum.nextElement(); ++ InputStream providerStream = null; ++ providerStream = url.openStream(); ++ ++ // Parse the XML and add the providers ++ loadFromXML(providerStream); ++ } ++ } ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } ++ ++ /** ++ * Parse the given input stream and load the providers specified in it ++ * and loads it into the provider manager. ++ * ++ * @param providerStream an input stream providing the xml content. ++ * @throws Exception if an error occurs. ++ */ ++ public void loadFromXML(InputStream providerStream) throws Exception { ++ try { ++ XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); ++ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); ++ parser.setInput(providerStream, "UTF-8"); ++ int eventType = parser.getEventType(); ++ do { ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("iqProvider")) { ++ parser.next(); ++ parser.next(); ++ String elementName = parser.nextText(); ++ parser.next(); ++ parser.next(); ++ String namespace = parser.nextText(); ++ parser.next(); ++ parser.next(); ++ String className = parser.nextText(); ++ // Only add the provider for the namespace if one isn't ++ // already registered. ++ String key = getProviderKey(elementName, namespace); ++ if (!iqProviders.containsKey(key)) { ++ // Attempt to load the provider class and then create ++ // a new instance if it's an IQProvider. Otherwise, if it's ++ // an IQ class, add the class object itself, then we'll use ++ // reflection later to create instances of the class. ++ try { ++ // Add the provider to the map. ++ Class provider = Class.forName(className); ++ if (IQProvider.class.isAssignableFrom(provider)) { ++ iqProviders.put(key, provider.newInstance()); ++ } ++ else if (IQ.class.isAssignableFrom(provider)) { ++ iqProviders.put(key, provider); ++ } ++ } ++ catch (ClassNotFoundException cnfe) { ++ cnfe.printStackTrace(); ++ } ++ } ++ } ++ else if (parser.getName().equals("extensionProvider")) { ++ parser.next(); ++ parser.next(); ++ String elementName = parser.nextText(); ++ parser.next(); ++ parser.next(); ++ String namespace = parser.nextText(); ++ parser.next(); ++ parser.next(); ++ String className = parser.nextText(); ++ // Only add the provider for the namespace if one isn't ++ // already registered. ++ String key = getProviderKey(elementName, namespace); ++ if (!extensionProviders.containsKey(key)) { ++ // Attempt to load the provider class and then create ++ // a new instance if it's a Provider. Otherwise, if it's ++ // a PacketExtension, add the class object itself and ++ // then we'll use reflection later to create instances ++ // of the class. ++ try { ++ // Add the provider to the map. ++ Class provider = Class.forName(className); ++ if (PacketExtensionProvider.class.isAssignableFrom( ++ provider)) { ++ extensionProviders.put(key, provider.newInstance()); ++ } ++ else if (PacketExtension.class.isAssignableFrom( ++ provider)) { ++ extensionProviders.put(key, provider); ++ } ++ } ++ catch (ClassNotFoundException cnfe) { ++ cnfe.printStackTrace(); ++ } ++ } ++ } ++ } ++ eventType = parser.next(); ++ } ++ while (eventType != XmlPullParser.END_DOCUMENT); ++ } ++ finally { ++ try { ++ providerStream.close(); ++ } ++ catch (Exception e) { ++ // Ignore. ++ } ++ } ++ } ++ ++ /** ++ * Returns the IQ provider registered to the specified XML element name and namespace. ++ * For example, if a provider was registered to the element name "query" and the ++ * namespace "jabber:iq:time", then the following packet would trigger the provider: ++ * ++ *

      ++     * <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'>
      ++     *     <query xmlns='jabber:iq:time'>
      ++     *         <utc>20020910T17:58:35</utc>
      ++     *         <tz>MDT</tz>
      ++     *         <display>Tue Sep 10 12:58:35 2002</display>
      ++     *     </query>
      ++     * </iq>
      ++ * ++ *

      Note: this method is generally only called by the internal Smack classes. ++ * ++ * @param elementName the XML element name. ++ * @param namespace the XML namespace. ++ * @return the IQ provider. ++ */ ++ public Object getIQProvider(String elementName, String namespace) { ++ String key = getProviderKey(elementName, namespace); ++ return iqProviders.get(key); ++ } ++ ++ /** ++ * Returns an unmodifiable collection of all IQProvider instances. Each object ++ * in the collection will either be an IQProvider instance, or a Class object ++ * that implements the IQProvider interface. ++ * ++ * @return all IQProvider instances. ++ */ ++ public Collection getIQProviders() { ++ return Collections.unmodifiableCollection(iqProviders.values()); ++ } ++ ++ /** ++ * Adds an IQ provider (must be an instance of IQProvider or Class object that is an IQ) ++ * with the specified element name and name space. The provider will override any providers ++ * loaded through the classpath. ++ * ++ * @param elementName the XML element name. ++ * @param namespace the XML namespace. ++ * @param provider the IQ provider. ++ */ ++ public void addIQProvider(String elementName, String namespace, ++ Object provider) ++ { ++ if (!(provider instanceof IQProvider || (provider instanceof Class && ++ IQ.class.isAssignableFrom((Class)provider)))) ++ { ++ throw new IllegalArgumentException("Provider must be an IQProvider " + ++ "or a Class instance."); ++ } ++ String key = getProviderKey(elementName, namespace); ++ iqProviders.put(key, provider); ++ } ++ ++ /** ++ * Removes an IQ provider with the specified element name and namespace. This ++ * method is typically called to cleanup providers that are programatically added ++ * using the {@link #addIQProvider(String, String, Object) addIQProvider} method. ++ * ++ * @param elementName the XML element name. ++ * @param namespace the XML namespace. ++ */ ++ public void removeIQProvider(String elementName, String namespace) { ++ String key = getProviderKey(elementName, namespace); ++ iqProviders.remove(key); ++ } ++ ++ /** ++ * Returns the packet extension provider registered to the specified XML element name ++ * and namespace. For example, if a provider was registered to the element name "x" and the ++ * namespace "jabber:x:event", then the following packet would trigger the provider: ++ * ++ *
      ++     * <message to='romeo@montague.net' id='message_1'>
      ++     *     <body>Art thou not Romeo, and a Montague?</body>
      ++     *     <x xmlns='jabber:x:event'>
      ++     *         <composing/>
      ++     *     </x>
      ++     * </message>
      ++ * ++ *

      Note: this method is generally only called by the internal Smack classes. ++ * ++ * @param elementName element name associated with extension provider. ++ * @param namespace namespace associated with extension provider. ++ * @return the extenion provider. ++ */ ++ public Object getExtensionProvider(String elementName, String namespace) { ++ String key = getProviderKey(elementName, namespace); ++ return extensionProviders.get(key); ++ } ++ ++ /** ++ * Adds an extension provider with the specified element name and name space. The provider ++ * will override any providers loaded through the classpath. The provider must be either ++ * a PacketExtensionProvider instance, or a Class object of a Javabean. ++ * ++ * @param elementName the XML element name. ++ * @param namespace the XML namespace. ++ * @param provider the extension provider. ++ */ ++ public void addExtensionProvider(String elementName, String namespace, ++ Object provider) ++ { ++ if (!(provider instanceof PacketExtensionProvider || provider instanceof Class)) { ++ throw new IllegalArgumentException("Provider must be a PacketExtensionProvider " + ++ "or a Class instance."); ++ } ++ String key = getProviderKey(elementName, namespace); ++ extensionProviders.put(key, provider); ++ } ++ ++ /** ++ * Removes an extension provider with the specified element name and namespace. This ++ * method is typically called to cleanup providers that are programatically added ++ * using the {@link #addExtensionProvider(String, String, Object) addExtensionProvider} method. ++ * ++ * @param elementName the XML element name. ++ * @param namespace the XML namespace. ++ */ ++ public void removeExtensionProvider(String elementName, String namespace) { ++ String key = getProviderKey(elementName, namespace); ++ extensionProviders.remove(key); ++ } ++ ++ /** ++ * Returns an unmodifiable collection of all PacketExtensionProvider instances. Each object ++ * in the collection will either be a PacketExtensionProvider instance, or a Class object ++ * that implements the PacketExtensionProvider interface. ++ * ++ * @return all PacketExtensionProvider instances. ++ */ ++ public Collection getExtensionProviders() { ++ return Collections.unmodifiableCollection(extensionProviders.values()); ++ } ++ ++ /** ++ * Returns a String key for a given element name and namespace. ++ * ++ * @param elementName the element name. ++ * @param namespace the namespace. ++ * @return a unique key for the element name and namespace pair. ++ */ ++ private String getProviderKey(String elementName, String namespace) { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(elementName).append("/><").append(namespace).append("/>"); ++ return buf.toString(); ++ } ++ ++ /** ++ * Returns an array of class loaders to load resources from. ++ * ++ * @return an array of ClassLoader instances. ++ */ ++ private ClassLoader[] getClassLoaders() { ++ ClassLoader[] classLoaders = new ClassLoader[2]; ++ classLoaders[0] = ProviderManager.class.getClassLoader(); ++ classLoaders[1] = Thread.currentThread().getContextClassLoader(); ++ // Clean up possible null values. Note that #getClassLoader may return a null value. ++ List loaders = new ArrayList(); ++ for (ClassLoader classLoader : classLoaders) { ++ if (classLoader != null) { ++ loaders.add(classLoader); ++ } ++ } ++ return loaders.toArray(new ClassLoader[loaders.size()]); ++ } ++ ++ private ProviderManager() { ++ super(); ++ initialize(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/package.html +new file mode 100644 +index 0000000..fccc383 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/package.html +@@ -0,0 +1 @@ ++Provides pluggable parsing of incoming IQ's and packet extensions. +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/DirectSocketFactory.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/DirectSocketFactory.java +new file mode 100644 +index 0000000..42433b4 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/DirectSocketFactory.java +@@ -0,0 +1,57 @@ ++package org.jivesoftware.smack.proxy; ++ ++import java.io.IOException; ++import java.net.InetAddress; ++import java.net.InetSocketAddress; ++import java.net.Proxy; ++import java.net.Socket; ++import java.net.UnknownHostException; ++import javax.net.SocketFactory; ++ ++/** ++ * SocketFactory for direct connection ++ * ++ * @author Atul Aggarwal ++ */ ++class DirectSocketFactory ++ extends SocketFactory ++{ ++ ++ public DirectSocketFactory() ++ { ++ } ++ ++ static private int roundrobin = 0; ++ ++ public Socket createSocket(String host, int port) ++ throws IOException, UnknownHostException ++ { ++ Socket newSocket = new Socket(Proxy.NO_PROXY); ++ InetAddress resolved[] = InetAddress.getAllByName(host); ++ newSocket.connect(new InetSocketAddress(resolved[(roundrobin++) % resolved.length],port)); ++ return newSocket; ++ } ++ ++ public Socket createSocket(String host ,int port, InetAddress localHost, ++ int localPort) ++ throws IOException, UnknownHostException ++ { ++ return new Socket(host,port,localHost,localPort); ++ } ++ ++ public Socket createSocket(InetAddress host, int port) ++ throws IOException ++ { ++ Socket newSocket = new Socket(Proxy.NO_PROXY); ++ newSocket.connect(new InetSocketAddress(host,port)); ++ return newSocket; ++ } ++ ++ public Socket createSocket( InetAddress address, int port, ++ InetAddress localAddress, int localPort) ++ throws IOException ++ { ++ return new Socket(address,port,localAddress,localPort); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/HTTPProxySocketFactory.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/HTTPProxySocketFactory.java +new file mode 100644 +index 0000000..9531f04 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/HTTPProxySocketFactory.java +@@ -0,0 +1,156 @@ ++package org.jivesoftware.smack.proxy; ++ ++import java.io.BufferedReader; ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.StringReader; ++import java.net.HttpURLConnection; ++import java.net.InetAddress; ++import java.net.Socket; ++import java.net.UnknownHostException; ++import javax.net.SocketFactory; ++import org.jivesoftware.smack.util.Base64; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++ ++/** ++ * Http Proxy Socket Factory which returns socket connected to Http Proxy ++ * ++ * @author Atul Aggarwal ++ */ ++class HTTPProxySocketFactory ++ extends SocketFactory ++{ ++ ++ private ProxyInfo proxy; ++ ++ public HTTPProxySocketFactory(ProxyInfo proxy) ++ { ++ this.proxy = proxy; ++ } ++ ++ public Socket createSocket(String host, int port) ++ throws IOException, UnknownHostException ++ { ++ return httpProxifiedSocket(host, port); ++ } ++ ++ public Socket createSocket(String host ,int port, InetAddress localHost, ++ int localPort) ++ throws IOException, UnknownHostException ++ { ++ return httpProxifiedSocket(host, port); ++ } ++ ++ public Socket createSocket(InetAddress host, int port) ++ throws IOException ++ { ++ return httpProxifiedSocket(host.getHostAddress(), port); ++ ++ } ++ ++ public Socket createSocket( InetAddress address, int port, ++ InetAddress localAddress, int localPort) ++ throws IOException ++ { ++ return httpProxifiedSocket(address.getHostAddress(), port); ++ } ++ ++ private Socket httpProxifiedSocket(String host, int port) ++ throws IOException ++ { ++ String proxyhost = proxy.getProxyAddress(); ++ int proxyPort = proxy.getProxyPort(); ++ Socket socket = new Socket(proxyhost,proxyPort); ++ String hostport = "CONNECT " + host + ":" + port; ++ String proxyLine; ++ String username = proxy.getProxyUsername(); ++ if (username == null) ++ { ++ proxyLine = ""; ++ } ++ else ++ { ++ String password = proxy.getProxyPassword(); ++ proxyLine = "\r\nProxy-Authorization: Basic " ++ + new String (Base64.encodeBytes((username + ":" ++ + password).getBytes("UTF-8"))); ++ } ++ socket.getOutputStream().write((hostport + " HTTP/1.1\r\nHost: " ++ + hostport + proxyLine + "\r\n\r\n").getBytes("UTF-8")); ++ ++ InputStream in = socket.getInputStream(); ++ StringBuilder got = new StringBuilder(100); ++ int nlchars = 0; ++ ++ while (true) ++ { ++ char c = (char) in.read(); ++ got.append(c); ++ if (got.length() > 1024) ++ { ++ throw new ProxyException(ProxyInfo.ProxyType.HTTP, "Recieved " + ++ "header of >1024 characters from " ++ + proxyhost + ", cancelling connection"); ++ } ++ if (c == -1) ++ { ++ throw new ProxyException(ProxyInfo.ProxyType.HTTP); ++ } ++ if ((nlchars == 0 || nlchars == 2) && c == '\r') ++ { ++ nlchars++; ++ } ++ else if ((nlchars == 1 || nlchars == 3) && c == '\n') ++ { ++ nlchars++; ++ } ++ else ++ { ++ nlchars = 0; ++ } ++ if (nlchars == 4) ++ { ++ break; ++ } ++ } ++ ++ if (nlchars != 4) ++ { ++ throw new ProxyException(ProxyInfo.ProxyType.HTTP, "Never " + ++ "received blank line from " ++ + proxyhost + ", cancelling connection"); ++ } ++ ++ String gotstr = got.toString(); ++ ++ BufferedReader br = new BufferedReader(new StringReader(gotstr)); ++ String response = br.readLine(); ++ ++ if (response == null) ++ { ++ throw new ProxyException(ProxyInfo.ProxyType.HTTP, "Empty proxy " + ++ "response from " + proxyhost + ", cancelling"); ++ } ++ ++ Matcher m = RESPONSE_PATTERN.matcher(response); ++ if (!m.matches()) ++ { ++ throw new ProxyException(ProxyInfo.ProxyType.HTTP , "Unexpected " + ++ "proxy response from " + proxyhost + ": " + response); ++ } ++ ++ int code = Integer.parseInt(m.group(1)); ++ ++ if (code != HttpURLConnection.HTTP_OK) ++ { ++ throw new ProxyException(ProxyInfo.ProxyType.HTTP); ++ } ++ ++ return socket; ++ } ++ ++ private static final Pattern RESPONSE_PATTERN ++ = Pattern.compile("HTTP/\\S+\\s(\\d+)\\s(.*)\\s*"); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/ProxyException.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/ProxyException.java +new file mode 100644 +index 0000000..3cb6135 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/ProxyException.java +@@ -0,0 +1,27 @@ ++package org.jivesoftware.smack.proxy; ++ ++import java.io.IOException; ++ ++/** ++ * An exception class to handle exceptions caused by proxy. ++ * ++ * @author Atul Aggarwal ++ */ ++public class ProxyException ++ extends IOException ++{ ++ public ProxyException(ProxyInfo.ProxyType type, String ex, Throwable cause) ++ { ++ super("Proxy Exception " + type.toString() + " : "+ex+", "+cause); ++ } ++ ++ public ProxyException(ProxyInfo.ProxyType type, String ex) ++ { ++ super("Proxy Exception " + type.toString() + " : "+ex); ++ } ++ ++ public ProxyException(ProxyInfo.ProxyType type) ++ { ++ super("Proxy Exception " + type.toString() + " : " + "Unknown Error"); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/ProxyInfo.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/ProxyInfo.java +new file mode 100644 +index 0000000..a85de0d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/ProxyInfo.java +@@ -0,0 +1,114 @@ ++package org.jivesoftware.smack.proxy; ++ ++import javax.net.SocketFactory; ++ ++/** ++ * Class which stores proxy information such as proxy type, host, port, ++ * authentication etc. ++ * ++ * @author Atul Aggarwal ++ */ ++ ++public class ProxyInfo ++{ ++ public static enum ProxyType ++ { ++ NONE, ++ HTTP, ++ SOCKS4, ++ SOCKS5 ++ } ++ ++ private String proxyAddress; ++ private int proxyPort; ++ private String proxyUsername; ++ private String proxyPassword; ++ private ProxyType proxyType; ++ ++ public ProxyInfo( ProxyType pType, String pHost, int pPort, String pUser, ++ String pPass) ++ { ++ this.proxyType = pType; ++ this.proxyAddress = pHost; ++ this.proxyPort = pPort; ++ this.proxyUsername = pUser; ++ this.proxyPassword = pPass; ++ } ++ ++ public static ProxyInfo forHttpProxy(String pHost, int pPort, String pUser, ++ String pPass) ++ { ++ return new ProxyInfo(ProxyType.HTTP, pHost, pPort, pUser, pPass); ++ } ++ ++ public static ProxyInfo forSocks4Proxy(String pHost, int pPort, String pUser, ++ String pPass) ++ { ++ return new ProxyInfo(ProxyType.SOCKS4, pHost, pPort, pUser, pPass); ++ } ++ ++ public static ProxyInfo forSocks5Proxy(String pHost, int pPort, String pUser, ++ String pPass) ++ { ++ return new ProxyInfo(ProxyType.SOCKS5, pHost, pPort, pUser, pPass); ++ } ++ ++ public static ProxyInfo forNoProxy() ++ { ++ return new ProxyInfo(ProxyType.NONE, null, 0, null, null); ++ } ++ ++ public static ProxyInfo forDefaultProxy() ++ { ++ return new ProxyInfo(ProxyType.NONE, null, 0, null, null); ++ } ++ ++ public ProxyType getProxyType() ++ { ++ return proxyType; ++ } ++ ++ public String getProxyAddress() ++ { ++ return proxyAddress; ++ } ++ ++ public int getProxyPort() ++ { ++ return proxyPort; ++ } ++ ++ public String getProxyUsername() ++ { ++ return proxyUsername; ++ } ++ ++ public String getProxyPassword() ++ { ++ return proxyPassword; ++ } ++ ++ public SocketFactory getSocketFactory() ++ { ++ if(proxyType == ProxyType.NONE) ++ { ++ return new DirectSocketFactory(); ++ } ++ else if(proxyType == ProxyType.HTTP) ++ { ++ return new HTTPProxySocketFactory(this); ++ } ++ else if(proxyType == ProxyType.SOCKS4) ++ { ++ return new Socks4ProxySocketFactory(this); ++ } ++ else if(proxyType == ProxyType.SOCKS5) ++ { ++ return new Socks5ProxySocketFactory(this); ++ } ++ else ++ { ++ return null; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/Socks4ProxySocketFactory.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/Socks4ProxySocketFactory.java +new file mode 100644 +index 0000000..010357e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/Socks4ProxySocketFactory.java +@@ -0,0 +1,199 @@ ++package org.jivesoftware.smack.proxy; ++ ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.OutputStream; ++import java.net.InetAddress; ++import java.net.Socket; ++import java.net.UnknownHostException; ++import javax.net.SocketFactory; ++ ++/** ++ * Socket factory for socks4 proxy ++ * ++ * @author Atul Aggarwal ++ */ ++public class Socks4ProxySocketFactory ++ extends SocketFactory ++{ ++ private ProxyInfo proxy; ++ ++ public Socks4ProxySocketFactory(ProxyInfo proxy) ++ { ++ this.proxy = proxy; ++ } ++ ++ public Socket createSocket(String host, int port) ++ throws IOException, UnknownHostException ++ { ++ return socks4ProxifiedSocket(host,port); ++ ++ } ++ ++ public Socket createSocket(String host ,int port, InetAddress localHost, ++ int localPort) ++ throws IOException, UnknownHostException ++ { ++ return socks4ProxifiedSocket(host,port); ++ } ++ ++ public Socket createSocket(InetAddress host, int port) ++ throws IOException ++ { ++ return socks4ProxifiedSocket(host.getHostAddress(),port); ++ } ++ ++ public Socket createSocket( InetAddress address, int port, ++ InetAddress localAddress, int localPort) ++ throws IOException ++ { ++ return socks4ProxifiedSocket(address.getHostAddress(),port); ++ ++ } ++ ++ private Socket socks4ProxifiedSocket(String host, int port) ++ throws IOException ++ { ++ Socket socket = null; ++ InputStream in = null; ++ OutputStream out = null; ++ String proxy_host = proxy.getProxyAddress(); ++ int proxy_port = proxy.getProxyPort(); ++ String user = proxy.getProxyUsername(); ++ String passwd = proxy.getProxyPassword(); ++ ++ try ++ { ++ socket=new Socket(proxy_host, proxy_port); ++ in=socket.getInputStream(); ++ out=socket.getOutputStream(); ++ socket.setTcpNoDelay(true); ++ ++ byte[] buf=new byte[1024]; ++ int index=0; ++ ++ /* ++ 1) CONNECT ++ ++ The client connects to the SOCKS server and sends a CONNECT request when ++ it wants to establish a connection to an application server. The client ++ includes in the request packet the IP address and the port number of the ++ destination host, and userid, in the following format. ++ ++ +----+----+----+----+----+----+----+----+----+----+....+----+ ++ | VN | CD | DSTPORT | DSTIP | USERID |NULL| ++ +----+----+----+----+----+----+----+----+----+----+....+----+ ++ # of bytes: 1 1 2 4 variable 1 ++ ++ VN is the SOCKS protocol version number and should be 4. CD is the ++ SOCKS command code and should be 1 for CONNECT request. NULL is a byte ++ of all zero bits. ++ */ ++ ++ index=0; ++ buf[index++]=4; ++ buf[index++]=1; ++ ++ buf[index++]=(byte)(port>>>8); ++ buf[index++]=(byte)(port&0xff); ++ ++ try ++ { ++ InetAddress addr=InetAddress.getByName(host); ++ byte[] byteAddress = addr.getAddress(); ++ for (int i = 0; i < byteAddress.length; i++) ++ { ++ buf[index++]=byteAddress[i]; ++ } ++ } ++ catch(UnknownHostException uhe) ++ { ++ throw new ProxyException(ProxyInfo.ProxyType.SOCKS4, ++ uhe.toString(), uhe); ++ } ++ ++ if(user!=null) ++ { ++ System.arraycopy(user.getBytes(), 0, buf, index, user.length()); ++ index+=user.length(); ++ } ++ buf[index++]=0; ++ out.write(buf, 0, index); ++ ++ /* ++ The SOCKS server checks to see whether such a request should be granted ++ based on any combination of source IP address, destination IP address, ++ destination port number, the userid, and information it may obtain by ++ consulting IDENT, cf. RFC 1413. If the request is granted, the SOCKS ++ server makes a connection to the specified port of the destination host. ++ A reply packet is sent to the client when this connection is established, ++ or when the request is rejected or the operation fails. ++ ++ +----+----+----+----+----+----+----+----+ ++ | VN | CD | DSTPORT | DSTIP | ++ +----+----+----+----+----+----+----+----+ ++ # of bytes: 1 1 2 4 ++ ++ VN is the version of the reply code and should be 0. CD is the result ++ code with one of the following values: ++ ++ 90: request granted ++ 91: request rejected or failed ++ 92: request rejected becasue SOCKS server cannot connect to ++ identd on the client ++ 93: request rejected because the client program and identd ++ report different user-ids ++ ++ The remaining fields are ignored. ++ */ ++ ++ int len=6; ++ int s=0; ++ while(s>>8); ++ buf[index++]=(byte)(port&0xff); ++ ++ out.write(buf, 0, index); ++ ++/* ++ The SOCKS request information is sent by the client as soon as it has ++ established a connection to the SOCKS server, and completed the ++ authentication negotiations. The server evaluates the request, and ++ returns a reply formed as follows: ++ ++ +----+-----+-------+------+----------+----------+ ++ |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | ++ +----+-----+-------+------+----------+----------+ ++ | 1 | 1 | X'00' | 1 | Variable | 2 | ++ +----+-----+-------+------+----------+----------+ ++ ++ Where: ++ ++ o VER protocol version: X'05' ++ o REP Reply field: ++ o X'00' succeeded ++ o X'01' general SOCKS server failure ++ o X'02' connection not allowed by ruleset ++ o X'03' Network unreachable ++ o X'04' Host unreachable ++ o X'05' Connection refused ++ o X'06' TTL expired ++ o X'07' Command not supported ++ o X'08' Address type not supported ++ o X'09' to X'FF' unassigned ++ o RSV RESERVED ++ o ATYP address type of following address ++ o IP V4 address: X'01' ++ o DOMAINNAME: X'03' ++ o IP V6 address: X'04' ++ o BND.ADDR server bound address ++ o BND.PORT server bound port in network octet order ++*/ ++ ++ //in.read(buf, 0, 4); ++ fill(in, buf, 4); ++ ++ if(buf[1]!=0) ++ { ++ try ++ { ++ socket.close(); ++ } ++ catch(Exception eee) ++ { ++ } ++ throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, ++ "server returns "+buf[1]); ++ } ++ ++ switch(buf[3]&0xff) ++ { ++ case 1: ++ //in.read(buf, 0, 6); ++ fill(in, buf, 6); ++ break; ++ case 3: ++ //in.read(buf, 0, 1); ++ fill(in, buf, 1); ++ //in.read(buf, 0, buf[0]+2); ++ fill(in, buf, (buf[0]&0xff)+2); ++ break; ++ case 4: ++ //in.read(buf, 0, 18); ++ fill(in, buf, 18); ++ break; ++ default: ++ } ++ return socket; ++ ++ } ++ catch(RuntimeException e) ++ { ++ throw e; ++ } ++ catch(Exception e) ++ { ++ try ++ { ++ if(socket!=null) ++ { ++ socket.close(); ++ } ++ } ++ catch(Exception eee) ++ { ++ } ++ String message="ProxySOCKS5: "+e.toString(); ++ if(e instanceof Throwable) ++ { ++ throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,message, ++ (Throwable)e); ++ } ++ throw new IOException(message); ++ } ++ } ++ ++ private void fill(InputStream in, byte[] buf, int len) ++ throws IOException ++ { ++ int s=0; ++ while(s ++ *
    • javax.net.ssl.keyStore to the location of the keyStore ++ *
    • javax.net.ssl.keyStorePassword to the password of the keyStore ++ *
    • javax.net.ssl.trustStore to the location of the trustStore ++ *
    • javax.net.ssl.trustStorePassword to the the password of the trustStore ++ * ++ * ++ * Then, when the server requests or requires the client certificate, java will ++ * simply provide the one in the keyStore. ++ * ++ * Also worth noting is the EXTERNAL mechanism in Smack is not enabled by default. ++ * To enable it, the implementer will need to call SASLAuthentication.supportSASLMechamism("EXTERNAL"); ++ * ++ * @author Jay Kline ++ */ ++public class SASLExternalMechanism extends SASLMechanism { ++ ++ public SASLExternalMechanism(SASLAuthentication saslAuthentication) { ++ super(saslAuthentication); ++ } ++ ++ protected String getName() { ++ return "EXTERNAL"; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLFacebookConnect.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLFacebookConnect.java +new file mode 100644 +index 0000000..3126d83 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLFacebookConnect.java +@@ -0,0 +1,201 @@ ++package org.jivesoftware.smack.sasl; ++ ++import java.io.IOException; ++import java.io.UnsupportedEncodingException; ++import java.net.URLEncoder; ++import java.security.MessageDigest; ++import java.security.NoSuchAlgorithmException; ++import java.util.GregorianCalendar; ++import java.util.HashMap; ++import java.util.Map; ++ ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++import de.measite.smack.Sasl; ++ ++import org.jivesoftware.smack.SASLAuthentication; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.sasl.SASLMechanism; ++import org.jivesoftware.smack.util.Base64; ++ ++/** ++ * This class is originally from http://code.google.com/p/fbgc/source/browse/trunk/daemon/src/main/java/org/albino/mechanisms/FacebookConnectSASLMechanism.java ++ * I just adapted to match the SMACK package scheme and ++ */ ++public class SASLFacebookConnect extends SASLMechanism { ++ ++ private String sessionKey = ""; ++ private String sessionSecret = ""; ++ private String apiKey = ""; ++ ++ static{ ++ SASLAuthentication.registerSASLMechanism("X-FACEBOOK-PLATFORM", ++ SASLFacebookConnect.class); ++ SASLAuthentication.supportSASLMechanism("X-FACEBOOK-PLATFORM", 0); ++ } ++ ++ public SASLFacebookConnect(SASLAuthentication saslAuthentication) { ++ super(saslAuthentication); ++ } ++ ++ // protected void authenticate() throws IOException, XMPPException { ++ // String[] mechanisms = { getName() }; ++ // Map props = new HashMap(); ++ // sc = Sasl.createSaslClient(mechanisms, null, "xmpp", hostname, props, ++ // this); ++ // ++ // super.authenticate(); ++ // } ++ ++ protected void authenticate() throws IOException, XMPPException { ++ final StringBuilder stanza = new StringBuilder(); ++ stanza.append(""); ++ stanza.append(""); ++ ++ // Send the authentication to the server ++ getSASLAuthentication().send(new Packet(){ ++ ++ @Override ++ public String toXML() { ++ return stanza.toString(); ++ } ++ ++ }); ++ } ++ ++ public void authenticate(String apiKeyAndSessionKey, String host, String sessionSecret) ++ throws IOException, XMPPException { ++ ++ if(apiKeyAndSessionKey==null || sessionSecret==null) ++ throw new IllegalStateException("Invalid parameters!"); ++ ++ String[] keyArray = apiKeyAndSessionKey.split("\\|"); ++ ++ if(keyArray==null || keyArray.length != 2) ++ throw new IllegalStateException("Api key or session key is not present!"); ++ ++ this.apiKey = keyArray[0]; ++ this.sessionKey = keyArray[1]; ++ this.sessionSecret = sessionSecret; ++ ++ this.authenticationId = sessionKey; ++ this.password = sessionSecret; ++ this.hostname = host; ++ ++ String[] mechanisms = { "DIGEST-MD5" }; ++ Map props = new HashMap(); ++ sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, this); ++ authenticate(); ++ } ++ ++ public void authenticate(String username, String host, CallbackHandler cbh) ++ throws IOException, XMPPException { ++ String[] mechanisms = { "DIGEST-MD5" }; ++ Map props = new HashMap(); ++ sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, cbh); ++ authenticate(); ++ } ++ ++ protected String getName() { ++ return "X-FACEBOOK-PLATFORM"; ++ } ++ ++ public void challengeReceived(String challenge) throws IOException { ++ // Build the challenge response stanza encoding the response text ++ final StringBuilder stanza = new StringBuilder(); ++ ++ byte response[] = null; ++ if (challenge != null) { ++ String decodedResponse = new String(Base64.decode(challenge)); ++ Map parameters = getQueryMap(decodedResponse); ++ ++ String version = "1.0"; ++ String nonce = parameters.get("nonce"); ++ String method = parameters.get("method"); ++ ++ Long callId = new GregorianCalendar().getTimeInMillis()/1000; ++ ++ String sig = "api_key="+apiKey ++ +"call_id="+callId ++ +"method="+method ++ +"nonce="+nonce ++ +"session_key="+sessionKey ++ +"v="+version ++ +sessionSecret; ++ ++ try { ++ sig = MD5(sig); ++ } catch (NoSuchAlgorithmException e) { ++ throw new IllegalStateException(e); ++ } ++ ++ String composedResponse = "api_key="+apiKey+"&" ++ +"call_id="+callId+"&" ++ +"method="+method+"&" ++ +"nonce="+nonce+"&" ++ +"session_key="+sessionKey+"&" ++ +"v="+version+"&" ++ +"sig="+sig; ++ ++ response = composedResponse.getBytes(); ++ } ++ ++ String authenticationText=""; ++ ++ if (response != null) { ++ authenticationText = Base64.encodeBytes(response, Base64.DONT_BREAK_LINES); ++ } ++ ++ stanza.append(""); ++ stanza.append(authenticationText); ++ stanza.append(""); ++ ++ // Send the authentication to the server ++ getSASLAuthentication().send(new Packet(){ ++ ++ @Override ++ public String toXML() { ++ return stanza.toString(); ++ } ++ ++ }); ++ } ++ ++ private Map getQueryMap(String query) { ++ String[] params = query.split("&"); ++ Map map = new HashMap(); ++ for (String param : params) { ++ String name = param.split("=")[0]; ++ String value = param.split("=")[1]; ++ map.put(name, value); ++ } ++ return map; ++ } ++ ++ private String convertToHex(byte[] data) { ++ StringBuffer buf = new StringBuffer(); ++ for (int i = 0; i < data.length; i++) { ++ int halfbyte = (data[i] >>> 4) & 0x0F; ++ int two_halfs = 0; ++ do { ++ if ((0 <= halfbyte) && (halfbyte <= 9)) ++ buf.append((char) ('0' + halfbyte)); ++ else ++ buf.append((char) ('a' + (halfbyte - 10))); ++ halfbyte = data[i] & 0x0F; ++ } while(two_halfs++ < 1); ++ } ++ return buf.toString(); ++ } ++ ++ public String MD5(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException { ++ MessageDigest md; ++ md = MessageDigest.getInstance("MD5"); ++ byte[] md5hash = new byte[32]; ++ md.update(text.getBytes("iso-8859-1"), 0, text.length()); ++ md5hash = md.digest(); ++ return convertToHex(md5hash); ++ } ++} ++ +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLGSSAPIMechanism.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLGSSAPIMechanism.java +new file mode 100644 +index 0000000..593ca09 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLGSSAPIMechanism.java +@@ -0,0 +1,90 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * ++ * All rights reserved. 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.SASLAuthentication; ++import org.jivesoftware.smack.XMPPException; ++ ++import java.io.IOException; ++import java.util.Map; ++import java.util.HashMap; ++import de.measite.smack.Sasl; ++import org.apache.harmony.javax.security.sasl.SaslClient; ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++ ++/** ++ * Implementation of the SASL GSSAPI mechanism ++ * ++ * @author Jay Kline ++ */ ++public class SASLGSSAPIMechanism extends SASLMechanism { ++ ++ public SASLGSSAPIMechanism(SASLAuthentication saslAuthentication) { ++ super(saslAuthentication); ++ ++ System.setProperty("javax.security.auth.useSubjectCredsOnly","false"); ++ System.setProperty("java.security.auth.login.config","gss.conf"); ++ ++ } ++ ++ protected String getName() { ++ return "GSSAPI"; ++ } ++ ++ /** ++ * Builds and sends the auth stanza to the server. ++ * This overrides from the abstract class because the initial token ++ * needed for GSSAPI is binary, and not safe to put in a string, thus ++ * getAuthenticationText() cannot be used. ++ * ++ * @param username the username of the user being authenticated. ++ * @param host the hostname where the user account resides. ++ * @param cbh the CallbackHandler (not used with GSSAPI) ++ * @throws IOException If a network error occures while authenticating. ++ */ ++ public void authenticate(String username, String host, CallbackHandler cbh) throws IOException, XMPPException { ++ String[] mechanisms = { getName() }; ++ Map props = new HashMap(); ++ props.put(Sasl.SERVER_AUTH,"TRUE"); ++ sc = Sasl.createSaslClient(mechanisms, username, "xmpp", host, props, cbh); ++ authenticate(); ++ } ++ ++ /** ++ * Builds and sends the auth stanza to the server. ++ * This overrides from the abstract class because the initial token ++ * needed for GSSAPI is binary, and not safe to put in a string, thus ++ * getAuthenticationText() cannot be used. ++ * ++ * @param username the username of the user being authenticated. ++ * @param host the hostname where the user account resides. ++ * @param password the password of the user (ignored for GSSAPI) ++ * @throws IOException If a network error occures while authenticating. ++ */ ++ public void authenticate(String username, String host, String password) throws IOException, XMPPException { ++ String[] mechanisms = { getName() }; ++ Map props = new HashMap(); ++ props.put(Sasl.SERVER_AUTH,"TRUE"); ++ sc = Sasl.createSaslClient(mechanisms, username, "xmpp", host, props, this); ++ authenticate(); ++ } ++ ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLMechanism.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLMechanism.java +new file mode 100644 +index 0000000..5a9ed09 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLMechanism.java +@@ -0,0 +1,323 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.XMPPException; ++import org.jivesoftware.smack.SASLAuthentication; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.util.Base64; ++ ++import java.io.IOException; ++import java.util.Map; ++import java.util.HashMap; ++import org.apache.harmony.javax.security.auth.callback.CallbackHandler; ++import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException; ++import org.apache.harmony.javax.security.auth.callback.Callback; ++import org.apache.harmony.javax.security.auth.callback.NameCallback; ++import org.apache.harmony.javax.security.auth.callback.PasswordCallback; ++import org.apache.harmony.javax.security.sasl.RealmCallback; ++import org.apache.harmony.javax.security.sasl.RealmChoiceCallback; ++import de.measite.smack.Sasl; ++import org.apache.harmony.javax.security.sasl.SaslClient; ++import org.apache.harmony.javax.security.sasl.SaslException; ++ ++/** ++ * Base class for SASL mechanisms. Subclasses must implement these methods: ++ *
        ++ *
      • {@link #getName()} -- returns the common name of the SASL mechanism.
      • ++ *
      ++ * Subclasses will likely want to implement their own versions of these mthods: ++ *
    • {@link #authenticate(String, String, String)} -- Initiate authentication stanza using the ++ * deprecated method.
    • ++ *
    • {@link #authenticate(String, String, CallbackHandler)} -- Initiate authentication stanza ++ * using the CallbackHandler method.
    • ++ *
    • {@link #challengeReceived(String)} -- Handle a challenge from the server.
    • ++ * ++ * ++ * @author Jay Kline ++ */ ++public abstract class SASLMechanism implements CallbackHandler { ++ ++ private SASLAuthentication saslAuthentication; ++ protected SaslClient sc; ++ protected String authenticationId; ++ protected String password; ++ protected String hostname; ++ ++ ++ public SASLMechanism(SASLAuthentication saslAuthentication) { ++ this.saslAuthentication = saslAuthentication; ++ } ++ ++ /** ++ * Builds and sends the auth stanza to the server. Note that this method of ++ * authentication is not recommended, since it is very inflexable. Use ++ * {@link #authenticate(String, String, CallbackHandler)} whenever possible. ++ * ++ * @param username the username of the user being authenticated. ++ * @param host the hostname where the user account resides. ++ * @param password the password for this account. ++ * @throws IOException If a network error occurs while authenticating. ++ * @throws XMPPException If a protocol error occurs or the user is not authenticated. ++ */ ++ public void authenticate(String username, String host, String password) throws IOException, XMPPException { ++ //Since we were not provided with a CallbackHandler, we will use our own with the given ++ //information ++ ++ //Set the authenticationID as the username, since they must be the same in this case. ++ this.authenticationId = username; ++ this.password = password; ++ this.hostname = host; ++ ++ String[] mechanisms = { getName() }; ++ Map props = new HashMap(); ++ sc = Sasl.createSaslClient(mechanisms, username, "xmpp", host, props, this); ++ authenticate(); ++ } ++ ++ /** ++ * Builds and sends the auth stanza to the server. The callback handler will handle ++ * any additional information, such as the authentication ID or realm, if it is needed. ++ * ++ * @param username the username of the user being authenticated. ++ * @param host the hostname where the user account resides. ++ * @param cbh the CallbackHandler to obtain user information. ++ * @throws IOException If a network error occures while authenticating. ++ * @throws XMPPException If a protocol error occurs or the user is not authenticated. ++ */ ++ public void authenticate(String username, String host, CallbackHandler cbh) throws IOException, XMPPException { ++ String[] mechanisms = { getName() }; ++ Map props = new HashMap(); ++ sc = Sasl.createSaslClient(mechanisms, username, "xmpp", host, props, cbh); ++ authenticate(); ++ } ++ ++ protected void authenticate() throws IOException, XMPPException { ++ String authenticationText = null; ++ try { ++ if(sc.hasInitialResponse()) { ++ byte[] response = sc.evaluateChallenge(new byte[0]); ++ authenticationText = Base64.encodeBytes(response,Base64.DONT_BREAK_LINES); ++ } ++ } catch (SaslException e) { ++ throw new XMPPException("SASL authentication failed", e); ++ } ++ ++ // Send the authentication to the server ++ getSASLAuthentication().send(new AuthMechanism(getName(), authenticationText)); ++ } ++ ++ ++ /** ++ * The server is challenging the SASL mechanism for the stanza he just sent. Send a ++ * response to the server's challenge. ++ * ++ * @param challenge a base64 encoded string representing the challenge. ++ * @throws IOException if an exception sending the response occurs. ++ */ ++ public void challengeReceived(String challenge) throws IOException { ++ byte response[]; ++ if(challenge != null) { ++ response = sc.evaluateChallenge(Base64.decode(challenge)); ++ } else { ++ response = sc.evaluateChallenge(new byte[0]); ++ } ++ ++ Packet responseStanza; ++ if (response == null) { ++ responseStanza = new Response(); ++ } ++ else { ++ responseStanza = new Response(Base64.encodeBytes(response,Base64.DONT_BREAK_LINES)); ++ } ++ ++ // Send the authentication to the server ++ getSASLAuthentication().send(responseStanza); ++ } ++ ++ /** ++ * Returns the common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or GSSAPI. ++ * ++ * @return the common name of the SASL mechanism. ++ */ ++ protected abstract String getName(); ++ ++ ++ protected SASLAuthentication getSASLAuthentication() { ++ return saslAuthentication; ++ } ++ ++ /** ++ * ++ */ ++ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { ++ for (int i = 0; i < callbacks.length; i++) { ++ if (callbacks[i] instanceof NameCallback) { ++ NameCallback ncb = (NameCallback)callbacks[i]; ++ ncb.setName(authenticationId); ++ } else if(callbacks[i] instanceof PasswordCallback) { ++ PasswordCallback pcb = (PasswordCallback)callbacks[i]; ++ pcb.setPassword(password.toCharArray()); ++ } else if(callbacks[i] instanceof RealmCallback) { ++ RealmCallback rcb = (RealmCallback)callbacks[i]; ++ rcb.setText(hostname); ++ } else if(callbacks[i] instanceof RealmChoiceCallback){ ++ //unused ++ //RealmChoiceCallback rccb = (RealmChoiceCallback)callbacks[i]; ++ } else { ++ throw new UnsupportedCallbackException(callbacks[i]); ++ } ++ } ++ } ++ ++ /** ++ * Initiating SASL authentication by select a mechanism. ++ */ ++ public class AuthMechanism extends Packet { ++ final private String name; ++ final private String authenticationText; ++ ++ public AuthMechanism(String name, String authenticationText) { ++ if (name == null) { ++ throw new NullPointerException("SASL mechanism name shouldn't be null."); ++ } ++ this.name = name; ++ this.authenticationText = authenticationText; ++ } ++ ++ public String toXML() { ++ StringBuilder stanza = new StringBuilder(); ++ stanza.append(""); ++ if (authenticationText != null && ++ authenticationText.trim().length() > 0) { ++ stanza.append(authenticationText); ++ } ++ stanza.append(""); ++ return stanza.toString(); ++ } ++ } ++ ++ /** ++ * A SASL challenge stanza. ++ */ ++ public static class Challenge extends Packet { ++ final private String data; ++ ++ public Challenge(String data) { ++ this.data = data; ++ } ++ ++ public String toXML() { ++ StringBuilder stanza = new StringBuilder(); ++ stanza.append(""); ++ if (data != null && ++ data.trim().length() > 0) { ++ stanza.append(data); ++ } ++ stanza.append(""); ++ return stanza.toString(); ++ } ++ } ++ ++ /** ++ * A SASL response stanza. ++ */ ++ public class Response extends Packet { ++ final private String authenticationText; ++ ++ public Response() { ++ authenticationText = null; ++ } ++ ++ public Response(String authenticationText) { ++ if (authenticationText == null || authenticationText.trim().length() == 0) { ++ this.authenticationText = null; ++ } ++ else { ++ this.authenticationText = authenticationText; ++ } ++ } ++ ++ public String toXML() { ++ StringBuilder stanza = new StringBuilder(); ++ stanza.append(""); ++ if (authenticationText != null) { ++ stanza.append(authenticationText); ++ } ++ stanza.append(""); ++ return stanza.toString(); ++ } ++ } ++ ++ /** ++ * A SASL success stanza. ++ */ ++ public static class Success extends Packet { ++ final private String data; ++ ++ public Success(String data) { ++ this.data = data; ++ } ++ ++ public String toXML() { ++ StringBuilder stanza = new StringBuilder(); ++ stanza.append(""); ++ if (data != null && ++ data.trim().length() > 0) { ++ stanza.append(data); ++ } ++ stanza.append(""); ++ return stanza.toString(); ++ } ++ } ++ ++ /** ++ * A SASL failure stanza. ++ */ ++ public static class Failure extends Packet { ++ final private String condition; ++ ++ public Failure(String condition) { ++ this.condition = condition; ++ } ++ ++ /** ++ * Get the SASL related error condition. ++ * ++ * @return the SASL related error condition. ++ */ ++ public String getCondition() { ++ return condition; ++ } ++ ++ public String toXML() { ++ StringBuilder stanza = new StringBuilder(); ++ stanza.append(""); ++ if (condition != null && ++ condition.trim().length() > 0) { ++ stanza.append("<").append(condition).append("/>"); ++ } ++ stanza.append(""); ++ return stanza.toString(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLPlainMechanism.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLPlainMechanism.java +new file mode 100644 +index 0000000..cd973eb +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLPlainMechanism.java +@@ -0,0 +1,34 @@ ++/** ++ * ++ * All rights reserved. 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.SASLAuthentication; ++ ++/** ++ * Implementation of the SASL PLAIN mechanism ++ * ++ * @author Jay Kline ++ */ ++public class SASLPlainMechanism extends SASLMechanism { ++ ++ public SASLPlainMechanism(SASLAuthentication saslAuthentication) { ++ super(saslAuthentication); ++ } ++ ++ protected String getName() { ++ return "PLAIN"; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/package.html +new file mode 100644 +index 0000000..1e8cfb7 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/package.html +@@ -0,0 +1 @@ ++SASL Mechanisms. +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/Base64.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/Base64.java +new file mode 100644 +index 0000000..b0ed3ed +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/Base64.java +@@ -0,0 +1,1767 @@ ++package org.jivesoftware.smack.util; ++ ++/** ++ *

      Encodes and decodes to and from Base64 notation.

      ++ *

      Homepage: http://iharder.net/base64.

      ++ * ++ *

      ++ * Change Log: ++ *

      ++ *
        ++ *
      • v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug ++ * when using very small files (~< 40 bytes).
      • ++ *
      • v2.2 - Added some helper methods for encoding/decoding directly from ++ * one file to the next. Also added a main() method to support command line ++ * encoding/decoding from one file to the next. Also added these Base64 dialects: ++ *
          ++ *
        1. The default is RFC3548 format.
        2. ++ *
        3. Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates ++ * URL and file name friendly format as described in Section 4 of RFC3548. ++ * http://www.faqs.org/rfcs/rfc3548.html
        4. ++ *
        5. Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates ++ * URL and file name friendly format that preserves lexical ordering as described ++ * in http://www.faqs.org/qa/rfcc-1940.html
        6. ++ *
        ++ * Special thanks to Jim Kellerman at http://www.powerset.com/ ++ * for contributing the new Base64 dialects. ++ *
      • ++ * ++ *
      • v2.1 - Cleaned up javadoc comments and unused variables and methods. Added ++ * some convenience methods for reading and writing to and from files.
      • ++ *
      • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems ++ * with other encodings (like EBCDIC).
      • ++ *
      • v2.0.1 - Fixed an error when decoding a single byte, that is, when the ++ * encoded data was a single byte.
      • ++ *
      • v2.0 - I got rid of methods that used booleans to set options. ++ * Now everything is more consolidated and cleaner. The code now detects ++ * when data that's being decoded is gzip-compressed and will decompress it ++ * automatically. Generally things are cleaner. You'll probably have to ++ * change some method calls that you were making to support the new ++ * options format (ints that you "OR" together).
      • ++ *
      • v1.5.1 - Fixed bug when decompressing and decoding to a ++ * byte[] using decode( String s, boolean gzipCompressed ). ++ * Added the ability to "suspend" encoding in the Output Stream so ++ * you can turn on and off the encoding if you need to embed base64 ++ * data in an otherwise "normal" stream (like an XML file).
      • ++ *
      • v1.5 - Output stream pases on flush() command but doesn't do anything itself. ++ * This helps when using GZIP streams. ++ * Added the ability to GZip-compress objects before encoding them.
      • ++ *
      • v1.4 - Added helper methods to read/write files.
      • ++ *
      • v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
      • ++ *
      • v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream ++ * where last buffer being read, if not completely full, was not returned.
      • ++ *
      • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
      • ++ *
      • v1.3.3 - Fixed I/O streams which were totally messed up.
      • ++ *
      ++ * ++ *

      ++ * I am placing this code in the Public Domain. Do with it as you will. ++ * This software comes with no guarantees or warranties but with ++ * plenty of well-wishing instead! ++ * Please visit http://iharder.net/base64 ++ * periodically to check for updates or to contribute improvements. ++ *

      ++ * ++ * @author Robert Harder ++ * @author rob@iharder.net ++ * @version 2.2.1 ++ */ ++public class Base64 ++{ ++ ++/* ******** P U B L I C F I E L D S ******** */ ++ ++ ++ /** No options specified. Value is zero. */ ++ public final static int NO_OPTIONS = 0; ++ ++ /** Specify encoding. */ ++ public final static int ENCODE = 1; ++ ++ ++ /** Specify decoding. */ ++ public final static int DECODE = 0; ++ ++ ++ /** Specify that data should be gzip-compressed. */ ++ public final static int GZIP = 2; ++ ++ ++ /** Don't break lines when encoding (violates strict Base64 specification) */ ++ public final static int DONT_BREAK_LINES = 8; ++ ++ /** ++ * Encode using Base64-like encoding that is URL- and Filename-safe as described ++ * in Section 4 of RFC3548: ++ * http://www.faqs.org/rfcs/rfc3548.html. ++ * It is important to note that data encoded this way is not officially valid Base64, ++ * or at the very least should not be called Base64 without also specifying that is ++ * was encoded using the URL- and Filename-safe dialect. ++ */ ++ public final static int URL_SAFE = 16; ++ ++ ++ /** ++ * Encode using the special "ordered" dialect of Base64 described here: ++ * http://www.faqs.org/qa/rfcc-1940.html. ++ */ ++ public final static int ORDERED = 32; ++ ++ ++/* ******** P R I V A T E F I E L D S ******** */ ++ ++ ++ /** Maximum line length (76) of Base64 output. */ ++ private final static int MAX_LINE_LENGTH = 76; ++ ++ ++ /** The equals sign (=) as a byte. */ ++ private final static byte EQUALS_SIGN = (byte)'='; ++ ++ ++ /** The new line character (\n) as a byte. */ ++ private final static byte NEW_LINE = (byte)'\n'; ++ ++ ++ /** Preferred encoding. */ ++ private final static String PREFERRED_ENCODING = "UTF-8"; ++ ++ ++ // I think I end up not using the BAD_ENCODING indicator. ++ //private final static byte BAD_ENCODING = -9; // Indicates error in encoding ++ private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding ++ private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding ++ ++ ++/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ ++ ++ /** The 64 valid Base64 values. */ ++ //private final static byte[] ALPHABET; ++ /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ ++ private final static byte[] _STANDARD_ALPHABET = ++ { ++ (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', ++ (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', ++ (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', ++ (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', ++ (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', ++ (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', ++ (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', ++ (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', ++ (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', ++ (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' ++ }; ++ ++ ++ /** ++ * Translates a Base64 value to either its 6-bit reconstruction value ++ * or a negative number indicating some other meaning. ++ **/ ++ private final static byte[] _STANDARD_DECODABET = ++ { ++ -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 ++ -5,-5, // Whitespace: Tab and Linefeed ++ -9,-9, // Decimal 11 - 12 ++ -5, // Whitespace: Carriage Return ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 ++ -9,-9,-9,-9,-9, // Decimal 27 - 31 ++ -5, // Whitespace: Space ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 ++ 62, // Plus sign at decimal 43 ++ -9,-9,-9, // Decimal 44 - 46 ++ 63, // Slash at decimal 47 ++ 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine ++ -9,-9,-9, // Decimal 58 - 60 ++ -1, // Equals sign at decimal 61 ++ -9,-9,-9, // Decimal 62 - 64 ++ 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' ++ 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' ++ -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 ++ 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' ++ 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' ++ -9,-9,-9,-9 // Decimal 123 - 126 ++ /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ ++ }; ++ ++ ++/* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ ++ ++ /** ++ * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: ++ * http://www.faqs.org/rfcs/rfc3548.html. ++ * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." ++ */ ++ private final static byte[] _URL_SAFE_ALPHABET = ++ { ++ (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', ++ (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', ++ (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', ++ (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', ++ (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', ++ (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', ++ (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', ++ (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', ++ (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', ++ (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_' ++ }; ++ ++ /** ++ * Used in decoding URL- and Filename-safe dialects of Base64. ++ */ ++ private final static byte[] _URL_SAFE_DECODABET = ++ { ++ -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 ++ -5,-5, // Whitespace: Tab and Linefeed ++ -9,-9, // Decimal 11 - 12 ++ -5, // Whitespace: Carriage Return ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 ++ -9,-9,-9,-9,-9, // Decimal 27 - 31 ++ -5, // Whitespace: Space ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 ++ -9, // Plus sign at decimal 43 ++ -9, // Decimal 44 ++ 62, // Minus sign at decimal 45 ++ -9, // Decimal 46 ++ -9, // Slash at decimal 47 ++ 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine ++ -9,-9,-9, // Decimal 58 - 60 ++ -1, // Equals sign at decimal 61 ++ -9,-9,-9, // Decimal 62 - 64 ++ 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' ++ 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' ++ -9,-9,-9,-9, // Decimal 91 - 94 ++ 63, // Underscore at decimal 95 ++ -9, // Decimal 96 ++ 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' ++ 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' ++ -9,-9,-9,-9 // Decimal 123 - 126 ++ /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ ++ }; ++ ++ ++ ++/* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ ++ ++ /** ++ * I don't get the point of this technique, but it is described here: ++ * http://www.faqs.org/qa/rfcc-1940.html. ++ */ ++ private final static byte[] _ORDERED_ALPHABET = ++ { ++ (byte)'-', ++ (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', ++ (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', ++ (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', ++ (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', ++ (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', ++ (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', ++ (byte)'_', ++ (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', ++ (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', ++ (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', ++ (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z' ++ }; ++ ++ /** ++ * Used in decoding the "ordered" dialect of Base64. ++ */ ++ private final static byte[] _ORDERED_DECODABET = ++ { ++ -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 ++ -5,-5, // Whitespace: Tab and Linefeed ++ -9,-9, // Decimal 11 - 12 ++ -5, // Whitespace: Carriage Return ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 ++ -9,-9,-9,-9,-9, // Decimal 27 - 31 ++ -5, // Whitespace: Space ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 ++ -9, // Plus sign at decimal 43 ++ -9, // Decimal 44 ++ 0, // Minus sign at decimal 45 ++ -9, // Decimal 46 ++ -9, // Slash at decimal 47 ++ 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine ++ -9,-9,-9, // Decimal 58 - 60 ++ -1, // Equals sign at decimal 61 ++ -9,-9,-9, // Decimal 62 - 64 ++ 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M' ++ 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z' ++ -9,-9,-9,-9, // Decimal 91 - 94 ++ 37, // Underscore at decimal 95 ++ -9, // Decimal 96 ++ 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm' ++ 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z' ++ -9,-9,-9,-9 // Decimal 123 - 126 ++ /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 ++ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ ++ }; ++ ++ ++/* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ ++ ++ ++ /** ++ * Returns one of the _SOMETHING_ALPHABET byte arrays depending on ++ * the options specified. ++ * It's possible, though silly, to specify ORDERED and URLSAFE ++ * in which case one of them will be picked, though there is ++ * no guarantee as to which one will be picked. ++ */ ++ private final static byte[] getAlphabet( int options ) ++ { ++ if( (options & URL_SAFE) == URL_SAFE ) return _URL_SAFE_ALPHABET; ++ else if( (options & ORDERED) == ORDERED ) return _ORDERED_ALPHABET; ++ else return _STANDARD_ALPHABET; ++ ++ } // end getAlphabet ++ ++ ++ /** ++ * Returns one of the _SOMETHING_DECODABET byte arrays depending on ++ * the options specified. ++ * It's possible, though silly, to specify ORDERED and URL_SAFE ++ * in which case one of them will be picked, though there is ++ * no guarantee as to which one will be picked. ++ */ ++ private final static byte[] getDecodabet( int options ) ++ { ++ if( (options & URL_SAFE) == URL_SAFE ) return _URL_SAFE_DECODABET; ++ else if( (options & ORDERED) == ORDERED ) return _ORDERED_DECODABET; ++ else return _STANDARD_DECODABET; ++ ++ } // end getAlphabet ++ ++ ++ ++ /** Defeats instantiation. */ ++ private Base64(){} ++ ++ ++ /** ++ * Encodes or decodes two files from the command line; ++ * feel free to delete this method (in fact you probably should) ++ * if you're embedding this code into a larger program. ++ */ ++ public final static void main( String[] args ) ++ { ++ if( args.length < 3 ){ ++ usage("Not enough arguments."); ++ } // end if: args.length < 3 ++ else { ++ String flag = args[0]; ++ String infile = args[1]; ++ String outfile = args[2]; ++ if( flag.equals( "-e" ) ){ ++ Base64.encodeFileToFile( infile, outfile ); ++ } // end if: encode ++ else if( flag.equals( "-d" ) ) { ++ Base64.decodeFileToFile( infile, outfile ); ++ } // end else if: decode ++ else { ++ usage( "Unknown flag: " + flag ); ++ } // end else ++ } // end else ++ } // end main ++ ++ /** ++ * Prints command line usage. ++ * ++ * @param msg A message to include with usage info. ++ */ ++ private final static void usage( String msg ) ++ { ++ System.err.println( msg ); ++ System.err.println( "Usage: java Base64 -e|-d inputfile outputfile" ); ++ } // end usage ++ ++ ++/* ******** E N C O D I N G M E T H O D S ******** */ ++ ++ ++ /** ++ * Encodes up to the first three bytes of array threeBytes ++ * and returns a four-byte array in Base64 notation. ++ * The actual number of significant bytes in your array is ++ * given by numSigBytes. ++ * The array threeBytes needs only be as big as ++ * numSigBytes. ++ * Code can reuse a byte array by passing a four-byte array as b4. ++ * ++ * @param b4 A reusable byte array to reduce array instantiation ++ * @param threeBytes the array to convert ++ * @param numSigBytes the number of significant bytes in your array ++ * @return four byte array in Base64 notation. ++ * @since 1.5.1 ++ */ ++ private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, int options ) ++ { ++ encode3to4( threeBytes, 0, numSigBytes, b4, 0, options ); ++ return b4; ++ } // end encode3to4 ++ ++ ++ /** ++ *

      Encodes up to three bytes of the array source ++ * and writes the resulting four Base64 bytes to destination. ++ * The source and destination arrays can be manipulated ++ * anywhere along their length by specifying ++ * srcOffset and destOffset. ++ * This method does not check to make sure your arrays ++ * are large enough to accomodate srcOffset + 3 for ++ * the source array or destOffset + 4 for ++ * the destination array. ++ * The actual number of significant bytes in your array is ++ * given by numSigBytes.

      ++ *

      This is the lowest level of the encoding methods with ++ * all possible parameters.

      ++ * ++ * @param source the array to convert ++ * @param srcOffset the index where conversion begins ++ * @param numSigBytes the number of significant bytes in your array ++ * @param destination the array to hold the conversion ++ * @param destOffset the index where output will be put ++ * @return the destination array ++ * @since 1.3 ++ */ ++ private static byte[] encode3to4( ++ byte[] source, int srcOffset, int numSigBytes, ++ byte[] destination, int destOffset, int options ) ++ { ++ byte[] ALPHABET = getAlphabet( options ); ++ ++ // 1 2 3 ++ // 01234567890123456789012345678901 Bit position ++ // --------000000001111111122222222 Array position from threeBytes ++ // --------| || || || | Six bit groups to index ALPHABET ++ // >>18 >>12 >> 6 >> 0 Right shift necessary ++ // 0x3f 0x3f 0x3f Additional AND ++ ++ // Create buffer with zero-padding if there are only one or two ++ // significant bytes passed in the array. ++ // We have to shift left 24 in order to flush out the 1's that appear ++ // when Java treats a value as negative that is cast from a byte to an int. ++ int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) ++ | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) ++ | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); ++ ++ switch( numSigBytes ) ++ { ++ case 3: ++ destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; ++ destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; ++ destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; ++ destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; ++ return destination; ++ ++ case 2: ++ destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; ++ destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; ++ destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; ++ destination[ destOffset + 3 ] = EQUALS_SIGN; ++ return destination; ++ ++ case 1: ++ destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; ++ destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; ++ destination[ destOffset + 2 ] = EQUALS_SIGN; ++ destination[ destOffset + 3 ] = EQUALS_SIGN; ++ return destination; ++ ++ default: ++ return destination; ++ } // end switch ++ } // end encode3to4 ++ ++ ++ ++ /** ++ * Serializes an object and returns the Base64-encoded ++ * version of that serialized object. If the object ++ * cannot be serialized or there is another error, ++ * the method will return null. ++ * The object is not GZip-compressed before being encoded. ++ * ++ * @param serializableObject The object to encode ++ * @return The Base64-encoded object ++ * @since 1.4 ++ */ ++ public static String encodeObject( java.io.Serializable serializableObject ) ++ { ++ return encodeObject( serializableObject, NO_OPTIONS ); ++ } // end encodeObject ++ ++ ++ ++ /** ++ * Serializes an object and returns the Base64-encoded ++ * version of that serialized object. If the object ++ * cannot be serialized or there is another error, ++ * the method will return null. ++ *

      ++ * Valid options:

      ++     *   GZIP: gzip-compresses object before encoding it.
      ++     *   DONT_BREAK_LINES: don't break lines at 76 characters
      ++     *     Note: Technically, this makes your encoding non-compliant.
      ++     * 
      ++ *

      ++ * Example: encodeObject( myObj, Base64.GZIP ) or ++ *

      ++ * Example: encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES ) ++ * ++ * @param serializableObject The object to encode ++ * @param options Specified options ++ * @return The Base64-encoded object ++ * @see Base64#GZIP ++ * @see Base64#DONT_BREAK_LINES ++ * @since 2.0 ++ */ ++ public static String encodeObject( java.io.Serializable serializableObject, int options ) ++ { ++ // Streams ++ java.io.ByteArrayOutputStream baos = null; ++ java.io.OutputStream b64os = null; ++ java.io.ObjectOutputStream oos = null; ++ java.util.zip.GZIPOutputStream gzos = null; ++ ++ // Isolate options ++ int gzip = (options & GZIP); ++ int dontBreakLines = (options & DONT_BREAK_LINES); ++ ++ try ++ { ++ // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream ++ baos = new java.io.ByteArrayOutputStream(); ++ b64os = new Base64.OutputStream( baos, ENCODE | options ); ++ ++ // GZip? ++ if( gzip == GZIP ) ++ { ++ gzos = new java.util.zip.GZIPOutputStream( b64os ); ++ oos = new java.io.ObjectOutputStream( gzos ); ++ } // end if: gzip ++ else ++ oos = new java.io.ObjectOutputStream( b64os ); ++ ++ oos.writeObject( serializableObject ); ++ } // end try ++ catch( java.io.IOException e ) ++ { ++ e.printStackTrace(); ++ return null; ++ } // end catch ++ finally ++ { ++ try{ oos.close(); } catch( Exception e ){} ++ try{ gzos.close(); } catch( Exception e ){} ++ try{ b64os.close(); } catch( Exception e ){} ++ try{ baos.close(); } catch( Exception e ){} ++ } // end finally ++ ++ // Return value according to relevant encoding. ++ try ++ { ++ return new String( baos.toByteArray(), PREFERRED_ENCODING ); ++ } // end try ++ catch (java.io.UnsupportedEncodingException uue) ++ { ++ return new String( baos.toByteArray() ); ++ } // end catch ++ ++ } // end encode ++ ++ ++ ++ /** ++ * Encodes a byte array into Base64 notation. ++ * Does not GZip-compress data. ++ * ++ * @param source The data to convert ++ * @since 1.4 ++ */ ++ public static String encodeBytes( byte[] source ) ++ { ++ return encodeBytes( source, 0, source.length, NO_OPTIONS ); ++ } // end encodeBytes ++ ++ ++ ++ /** ++ * Encodes a byte array into Base64 notation. ++ *

      ++ * Valid options:

      ++     *   GZIP: gzip-compresses object before encoding it.
      ++     *   DONT_BREAK_LINES: don't break lines at 76 characters
      ++     *     Note: Technically, this makes your encoding non-compliant.
      ++     * 
      ++ *

      ++ * Example: encodeBytes( myData, Base64.GZIP ) or ++ *

      ++ * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) ++ * ++ * ++ * @param source The data to convert ++ * @param options Specified options ++ * @see Base64#GZIP ++ * @see Base64#DONT_BREAK_LINES ++ * @since 2.0 ++ */ ++ public static String encodeBytes( byte[] source, int options ) ++ { ++ return encodeBytes( source, 0, source.length, options ); ++ } // end encodeBytes ++ ++ ++ /** ++ * Encodes a byte array into Base64 notation. ++ * Does not GZip-compress data. ++ * ++ * @param source The data to convert ++ * @param off Offset in array where conversion should begin ++ * @param len Length of data to convert ++ * @since 1.4 ++ */ ++ public static String encodeBytes( byte[] source, int off, int len ) ++ { ++ return encodeBytes( source, off, len, NO_OPTIONS ); ++ } // end encodeBytes ++ ++ ++ ++ /** ++ * Encodes a byte array into Base64 notation. ++ *

      ++ * Valid options:

      ++     *   GZIP: gzip-compresses object before encoding it.
      ++     *   DONT_BREAK_LINES: don't break lines at 76 characters
      ++     *     Note: Technically, this makes your encoding non-compliant.
      ++     * 
      ++ *

      ++ * Example: encodeBytes( myData, Base64.GZIP ) or ++ *

      ++ * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) ++ * ++ * ++ * @param source The data to convert ++ * @param off Offset in array where conversion should begin ++ * @param len Length of data to convert ++ * @param options Specified options; alphabet type is pulled from this (standard, url-safe, ordered) ++ * @see Base64#GZIP ++ * @see Base64#DONT_BREAK_LINES ++ * @since 2.0 ++ */ ++ public static String encodeBytes( byte[] source, int off, int len, int options ) ++ { ++ // Isolate options ++ int dontBreakLines = ( options & DONT_BREAK_LINES ); ++ int gzip = ( options & GZIP ); ++ ++ // Compress? ++ if( gzip == GZIP ) ++ { ++ java.io.ByteArrayOutputStream baos = null; ++ java.util.zip.GZIPOutputStream gzos = null; ++ Base64.OutputStream b64os = null; ++ ++ ++ try ++ { ++ // GZip -> Base64 -> ByteArray ++ baos = new java.io.ByteArrayOutputStream(); ++ b64os = new Base64.OutputStream( baos, ENCODE | options ); ++ gzos = new java.util.zip.GZIPOutputStream( b64os ); ++ ++ gzos.write( source, off, len ); ++ gzos.close(); ++ } // end try ++ catch( java.io.IOException e ) ++ { ++ e.printStackTrace(); ++ return null; ++ } // end catch ++ finally ++ { ++ try{ gzos.close(); } catch( Exception e ){} ++ try{ b64os.close(); } catch( Exception e ){} ++ try{ baos.close(); } catch( Exception e ){} ++ } // end finally ++ ++ // Return value according to relevant encoding. ++ try ++ { ++ return new String( baos.toByteArray(), PREFERRED_ENCODING ); ++ } // end try ++ catch (java.io.UnsupportedEncodingException uue) ++ { ++ return new String( baos.toByteArray() ); ++ } // end catch ++ } // end if: compress ++ ++ // Else, don't compress. Better not to use streams at all then. ++ else ++ { ++ // Convert option to boolean in way that code likes it. ++ boolean breakLines = dontBreakLines == 0; ++ ++ int len43 = len * 4 / 3; ++ byte[] outBuff = new byte[ ( len43 ) // Main 4:3 ++ + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding ++ + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines ++ int d = 0; ++ int e = 0; ++ int len2 = len - 2; ++ int lineLength = 0; ++ for( ; d < len2; d+=3, e+=4 ) ++ { ++ encode3to4( source, d+off, 3, outBuff, e, options ); ++ ++ lineLength += 4; ++ if( breakLines && lineLength == MAX_LINE_LENGTH ) ++ { ++ outBuff[e+4] = NEW_LINE; ++ e++; ++ lineLength = 0; ++ } // end if: end of line ++ } // en dfor: each piece of array ++ ++ if( d < len ) ++ { ++ encode3to4( source, d+off, len - d, outBuff, e, options ); ++ e += 4; ++ } // end if: some padding needed ++ ++ ++ // Return value according to relevant encoding. ++ try ++ { ++ return new String( outBuff, 0, e, PREFERRED_ENCODING ); ++ } // end try ++ catch (java.io.UnsupportedEncodingException uue) ++ { ++ return new String( outBuff, 0, e ); ++ } // end catch ++ ++ } // end else: don't compress ++ ++ } // end encodeBytes ++ ++ ++ ++ ++ ++/* ******** D E C O D I N G M E T H O D S ******** */ ++ ++ ++ /** ++ * Decodes four bytes from array source ++ * and writes the resulting bytes (up to three of them) ++ * to destination. ++ * The source and destination arrays can be manipulated ++ * anywhere along their length by specifying ++ * srcOffset and destOffset. ++ * This method does not check to make sure your arrays ++ * are large enough to accomodate srcOffset + 4 for ++ * the source array or destOffset + 3 for ++ * the destination array. ++ * This method returns the actual number of bytes that ++ * were converted from the Base64 encoding. ++ *

      This is the lowest level of the decoding methods with ++ * all possible parameters.

      ++ * ++ * ++ * @param source the array to convert ++ * @param srcOffset the index where conversion begins ++ * @param destination the array to hold the conversion ++ * @param destOffset the index where output will be put ++ * @param options alphabet type is pulled from this (standard, url-safe, ordered) ++ * @return the number of decoded bytes converted ++ * @since 1.3 ++ */ ++ private static int decode4to3( byte[] source, int srcOffset, byte[] destination, int destOffset, int options ) ++ { ++ byte[] DECODABET = getDecodabet( options ); ++ ++ // Example: Dk== ++ if( source[ srcOffset + 2] == EQUALS_SIGN ) ++ { ++ // Two ways to do the same thing. Don't know which way I like best. ++ //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) ++ // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); ++ int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) ++ | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); ++ ++ destination[ destOffset ] = (byte)( outBuff >>> 16 ); ++ return 1; ++ } ++ ++ // Example: DkL= ++ else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) ++ { ++ // Two ways to do the same thing. Don't know which way I like best. ++ //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) ++ // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) ++ // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); ++ int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) ++ | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) ++ | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); ++ ++ destination[ destOffset ] = (byte)( outBuff >>> 16 ); ++ destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); ++ return 2; ++ } ++ ++ // Example: DkLE ++ else ++ { ++ try{ ++ // Two ways to do the same thing. Don't know which way I like best. ++ //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) ++ // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) ++ // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) ++ // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); ++ int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) ++ | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) ++ | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) ++ | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); ++ ++ ++ destination[ destOffset ] = (byte)( outBuff >> 16 ); ++ destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); ++ destination[ destOffset + 2 ] = (byte)( outBuff ); ++ ++ return 3; ++ }catch( Exception e){ ++ System.out.println(""+source[srcOffset]+ ": " + ( DECODABET[ source[ srcOffset ] ] ) ); ++ System.out.println(""+source[srcOffset+1]+ ": " + ( DECODABET[ source[ srcOffset + 1 ] ] ) ); ++ System.out.println(""+source[srcOffset+2]+ ": " + ( DECODABET[ source[ srcOffset + 2 ] ] ) ); ++ System.out.println(""+source[srcOffset+3]+ ": " + ( DECODABET[ source[ srcOffset + 3 ] ] ) ); ++ return -1; ++ } // end catch ++ } ++ } // end decodeToBytes ++ ++ ++ ++ ++ /** ++ * Very low-level access to decoding ASCII characters in ++ * the form of a byte array. Does not support automatically ++ * gunzipping or any other "fancy" features. ++ * ++ * @param source The Base64 encoded data ++ * @param off The offset of where to begin decoding ++ * @param len The length of characters to decode ++ * @return decoded data ++ * @since 1.3 ++ */ ++ public static byte[] decode( byte[] source, int off, int len, int options ) ++ { ++ byte[] DECODABET = getDecodabet( options ); ++ ++ int len34 = len * 3 / 4; ++ byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output ++ int outBuffPosn = 0; ++ ++ byte[] b4 = new byte[4]; ++ int b4Posn = 0; ++ int i = 0; ++ byte sbiCrop = 0; ++ byte sbiDecode = 0; ++ for( i = off; i < off+len; i++ ) ++ { ++ sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits ++ sbiDecode = DECODABET[ sbiCrop ]; ++ ++ if( sbiDecode >= WHITE_SPACE_ENC ) // White space, Equals sign or better ++ { ++ if( sbiDecode >= EQUALS_SIGN_ENC ) ++ { ++ b4[ b4Posn++ ] = sbiCrop; ++ if( b4Posn > 3 ) ++ { ++ outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options ); ++ b4Posn = 0; ++ ++ // If that was the equals sign, break out of 'for' loop ++ if( sbiCrop == EQUALS_SIGN ) ++ break; ++ } // end if: quartet built ++ ++ } // end if: equals sign or better ++ ++ } // end if: white space, equals sign or better ++ else ++ { ++ System.err.println( "Bad Base64 input character at " + i + ": " + source[i] + "(decimal)" ); ++ return null; ++ } // end else: ++ } // each input character ++ ++ byte[] out = new byte[ outBuffPosn ]; ++ System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); ++ return out; ++ } // end decode ++ ++ ++ ++ ++ /** ++ * Decodes data from Base64 notation, automatically ++ * detecting gzip-compressed data and decompressing it. ++ * ++ * @param s the string to decode ++ * @return the decoded data ++ * @since 1.4 ++ */ ++ public static byte[] decode( String s ) ++ { ++ return decode( s, NO_OPTIONS ); ++ } ++ ++ ++ /** ++ * Decodes data from Base64 notation, automatically ++ * detecting gzip-compressed data and decompressing it. ++ * ++ * @param s the string to decode ++ * @param options encode options such as URL_SAFE ++ * @return the decoded data ++ * @since 1.4 ++ */ ++ public static byte[] decode( String s, int options ) ++ { ++ byte[] bytes; ++ try ++ { ++ bytes = s.getBytes( PREFERRED_ENCODING ); ++ } // end try ++ catch( java.io.UnsupportedEncodingException uee ) ++ { ++ bytes = s.getBytes(); ++ } // end catch ++ // ++ ++ // Decode ++ bytes = decode( bytes, 0, bytes.length, options ); ++ ++ ++ // Check to see if it's gzip-compressed ++ // GZIP Magic Two-Byte Number: 0x8b1f (35615) ++ if( bytes != null && bytes.length >= 4 ) ++ { ++ ++ int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); ++ if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) ++ { ++ java.io.ByteArrayInputStream bais = null; ++ java.util.zip.GZIPInputStream gzis = null; ++ java.io.ByteArrayOutputStream baos = null; ++ byte[] buffer = new byte[2048]; ++ int length = 0; ++ ++ try ++ { ++ baos = new java.io.ByteArrayOutputStream(); ++ bais = new java.io.ByteArrayInputStream( bytes ); ++ gzis = new java.util.zip.GZIPInputStream( bais ); ++ ++ while( ( length = gzis.read( buffer ) ) >= 0 ) ++ { ++ baos.write(buffer,0,length); ++ } // end while: reading input ++ ++ // No error? Get new bytes. ++ bytes = baos.toByteArray(); ++ ++ } // end try ++ catch( java.io.IOException e ) ++ { ++ // Just return originally-decoded bytes ++ } // end catch ++ finally ++ { ++ try{ baos.close(); } catch( Exception e ){} ++ try{ gzis.close(); } catch( Exception e ){} ++ try{ bais.close(); } catch( Exception e ){} ++ } // end finally ++ ++ } // end if: gzipped ++ } // end if: bytes.length >= 2 ++ ++ return bytes; ++ } // end decode ++ ++ ++ ++ ++ /** ++ * Attempts to decode Base64 data and deserialize a Java ++ * Object within. Returns null if there was an error. ++ * ++ * @param encodedObject The Base64 data to decode ++ * @return The decoded and deserialized object ++ * @since 1.5 ++ */ ++ public static Object decodeToObject( String encodedObject ) ++ { ++ // Decode and gunzip if necessary ++ byte[] objBytes = decode( encodedObject ); ++ ++ java.io.ByteArrayInputStream bais = null; ++ java.io.ObjectInputStream ois = null; ++ Object obj = null; ++ ++ try ++ { ++ bais = new java.io.ByteArrayInputStream( objBytes ); ++ ois = new java.io.ObjectInputStream( bais ); ++ ++ obj = ois.readObject(); ++ } // end try ++ catch( java.io.IOException e ) ++ { ++ e.printStackTrace(); ++ obj = null; ++ } // end catch ++ catch( java.lang.ClassNotFoundException e ) ++ { ++ e.printStackTrace(); ++ obj = null; ++ } // end catch ++ finally ++ { ++ try{ bais.close(); } catch( Exception e ){} ++ try{ ois.close(); } catch( Exception e ){} ++ } // end finally ++ ++ return obj; ++ } // end decodeObject ++ ++ ++ ++ /** ++ * Convenience method for encoding data to a file. ++ * ++ * @param dataToEncode byte array of data to encode in base64 form ++ * @param filename Filename for saving encoded data ++ * @return true if successful, false otherwise ++ * ++ * @since 2.1 ++ */ ++ public static boolean encodeToFile( byte[] dataToEncode, String filename ) ++ { ++ boolean success = false; ++ Base64.OutputStream bos = null; ++ try ++ { ++ bos = new Base64.OutputStream( ++ new java.io.FileOutputStream( filename ), Base64.ENCODE ); ++ bos.write( dataToEncode ); ++ success = true; ++ } // end try ++ catch( java.io.IOException e ) ++ { ++ ++ success = false; ++ } // end catch: IOException ++ finally ++ { ++ try{ bos.close(); } catch( Exception e ){} ++ } // end finally ++ ++ return success; ++ } // end encodeToFile ++ ++ ++ /** ++ * Convenience method for decoding data to a file. ++ * ++ * @param dataToDecode Base64-encoded data as a string ++ * @param filename Filename for saving decoded data ++ * @return true if successful, false otherwise ++ * ++ * @since 2.1 ++ */ ++ public static boolean decodeToFile( String dataToDecode, String filename ) ++ { ++ boolean success = false; ++ Base64.OutputStream bos = null; ++ try ++ { ++ bos = new Base64.OutputStream( ++ new java.io.FileOutputStream( filename ), Base64.DECODE ); ++ bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); ++ success = true; ++ } // end try ++ catch( java.io.IOException e ) ++ { ++ success = false; ++ } // end catch: IOException ++ finally ++ { ++ try{ bos.close(); } catch( Exception e ){} ++ } // end finally ++ ++ return success; ++ } // end decodeToFile ++ ++ ++ ++ ++ /** ++ * Convenience method for reading a base64-encoded ++ * file and decoding it. ++ * ++ * @param filename Filename for reading encoded data ++ * @return decoded byte array or null if unsuccessful ++ * ++ * @since 2.1 ++ */ ++ public static byte[] decodeFromFile( String filename ) ++ { ++ byte[] decodedData = null; ++ Base64.InputStream bis = null; ++ try ++ { ++ // Set up some useful variables ++ java.io.File file = new java.io.File( filename ); ++ byte[] buffer = null; ++ int length = 0; ++ int numBytes = 0; ++ ++ // Check for size of file ++ if( file.length() > Integer.MAX_VALUE ) ++ { ++ System.err.println( "File is too big for this convenience method (" + file.length() + " bytes)." ); ++ return null; ++ } // end if: file too big for int index ++ buffer = new byte[ (int)file.length() ]; ++ ++ // Open a stream ++ bis = new Base64.InputStream( ++ new java.io.BufferedInputStream( ++ new java.io.FileInputStream( file ) ), Base64.DECODE ); ++ ++ // Read until done ++ while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) ++ length += numBytes; ++ ++ // Save in a variable to return ++ decodedData = new byte[ length ]; ++ System.arraycopy( buffer, 0, decodedData, 0, length ); ++ ++ } // end try ++ catch( java.io.IOException e ) ++ { ++ System.err.println( "Error decoding from file " + filename ); ++ } // end catch: IOException ++ finally ++ { ++ try{ bis.close(); } catch( Exception e) {} ++ } // end finally ++ ++ return decodedData; ++ } // end decodeFromFile ++ ++ ++ ++ /** ++ * Convenience method for reading a binary file ++ * and base64-encoding it. ++ * ++ * @param filename Filename for reading binary data ++ * @return base64-encoded string or null if unsuccessful ++ * ++ * @since 2.1 ++ */ ++ public static String encodeFromFile( String filename ) ++ { ++ String encodedData = null; ++ Base64.InputStream bis = null; ++ try ++ { ++ // Set up some useful variables ++ java.io.File file = new java.io.File( filename ); ++ byte[] buffer = new byte[ Math.max((int)(file.length() * 1.4),40) ]; // Need max() for math on small files (v2.2.1) ++ int length = 0; ++ int numBytes = 0; ++ ++ // Open a stream ++ bis = new Base64.InputStream( ++ new java.io.BufferedInputStream( ++ new java.io.FileInputStream( file ) ), Base64.ENCODE ); ++ ++ // Read until done ++ while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) ++ length += numBytes; ++ ++ // Save in a variable to return ++ encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); ++ ++ } // end try ++ catch( java.io.IOException e ) ++ { ++ System.err.println( "Error encoding from file " + filename ); ++ } // end catch: IOException ++ finally ++ { ++ try{ bis.close(); } catch( Exception e) {} ++ } // end finally ++ ++ return encodedData; ++ } // end encodeFromFile ++ ++ /** ++ * Reads infile and encodes it to outfile. ++ * ++ * @param infile Input file ++ * @param outfile Output file ++ * @since 2.2 ++ */ ++ public static void encodeFileToFile( String infile, String outfile ) ++ { ++ String encoded = Base64.encodeFromFile( infile ); ++ java.io.OutputStream out = null; ++ try{ ++ out = new java.io.BufferedOutputStream( ++ new java.io.FileOutputStream( outfile ) ); ++ out.write( encoded.getBytes("US-ASCII") ); // Strict, 7-bit output. ++ } // end try ++ catch( java.io.IOException ex ) { ++ ex.printStackTrace(); ++ } // end catch ++ finally { ++ try { out.close(); } ++ catch( Exception ex ){} ++ } // end finally ++ } // end encodeFileToFile ++ ++ ++ /** ++ * Reads infile and decodes it to outfile. ++ * ++ * @param infile Input file ++ * @param outfile Output file ++ * @since 2.2 ++ */ ++ public static void decodeFileToFile( String infile, String outfile ) ++ { ++ byte[] decoded = Base64.decodeFromFile( infile ); ++ java.io.OutputStream out = null; ++ try{ ++ out = new java.io.BufferedOutputStream( ++ new java.io.FileOutputStream( outfile ) ); ++ out.write( decoded ); ++ } // end try ++ catch( java.io.IOException ex ) { ++ ex.printStackTrace(); ++ } // end catch ++ finally { ++ try { out.close(); } ++ catch( Exception ex ){} ++ } // end finally ++ } // end decodeFileToFile ++ ++ ++ /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ ++ ++ ++ ++ /** ++ * A {@link Base64.InputStream} will read data from another ++ * java.io.InputStream, given in the constructor, ++ * and encode/decode to/from Base64 notation on the fly. ++ * ++ * @see Base64 ++ * @since 1.3 ++ */ ++ public static class InputStream extends java.io.FilterInputStream ++ { ++ private boolean encode; // Encoding or decoding ++ private int position; // Current position in the buffer ++ private byte[] buffer; // Small buffer holding converted data ++ private int bufferLength; // Length of buffer (3 or 4) ++ private int numSigBytes; // Number of meaningful bytes in the buffer ++ private int lineLength; ++ private boolean breakLines; // Break lines at less than 80 characters ++ private int options; // Record options used to create the stream. ++ private byte[] alphabet; // Local copies to avoid extra method calls ++ private byte[] decodabet; // Local copies to avoid extra method calls ++ ++ ++ /** ++ * Constructs a {@link Base64.InputStream} in DECODE mode. ++ * ++ * @param in the java.io.InputStream from which to read data. ++ * @since 1.3 ++ */ ++ public InputStream( java.io.InputStream in ) ++ { ++ this( in, DECODE ); ++ } // end constructor ++ ++ ++ /** ++ * Constructs a {@link Base64.InputStream} in ++ * either ENCODE or DECODE mode. ++ *

      ++ * Valid options:

      ++         *   ENCODE or DECODE: Encode or Decode as data is read.
      ++         *   DONT_BREAK_LINES: don't break lines at 76 characters
      ++         *     (only meaningful when encoding)
      ++         *     Note: Technically, this makes your encoding non-compliant.
      ++         * 
      ++ *

      ++ * Example: new Base64.InputStream( in, Base64.DECODE ) ++ * ++ * ++ * @param in the java.io.InputStream from which to read data. ++ * @param options Specified options ++ * @see Base64#ENCODE ++ * @see Base64#DECODE ++ * @see Base64#DONT_BREAK_LINES ++ * @since 2.0 ++ */ ++ public InputStream( java.io.InputStream in, int options ) ++ { ++ super( in ); ++ this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; ++ this.encode = (options & ENCODE) == ENCODE; ++ this.bufferLength = encode ? 4 : 3; ++ this.buffer = new byte[ bufferLength ]; ++ this.position = -1; ++ this.lineLength = 0; ++ this.options = options; // Record for later, mostly to determine which alphabet to use ++ this.alphabet = getAlphabet(options); ++ this.decodabet = getDecodabet(options); ++ } // end constructor ++ ++ /** ++ * Reads enough of the input stream to convert ++ * to/from Base64 and returns the next byte. ++ * ++ * @return next byte ++ * @since 1.3 ++ */ ++ public int read() throws java.io.IOException ++ { ++ // Do we need to get data? ++ if( position < 0 ) ++ { ++ if( encode ) ++ { ++ byte[] b3 = new byte[3]; ++ int numBinaryBytes = 0; ++ for( int i = 0; i < 3; i++ ) ++ { ++ try ++ { ++ int b = in.read(); ++ ++ // If end of stream, b is -1. ++ if( b >= 0 ) ++ { ++ b3[i] = (byte)b; ++ numBinaryBytes++; ++ } // end if: not end of stream ++ ++ } // end try: read ++ catch( java.io.IOException e ) ++ { ++ // Only a problem if we got no data at all. ++ if( i == 0 ) ++ throw e; ++ ++ } // end catch ++ } // end for: each needed input byte ++ ++ if( numBinaryBytes > 0 ) ++ { ++ encode3to4( b3, 0, numBinaryBytes, buffer, 0, options ); ++ position = 0; ++ numSigBytes = 4; ++ } // end if: got data ++ else ++ { ++ return -1; ++ } // end else ++ } // end if: encoding ++ ++ // Else decoding ++ else ++ { ++ byte[] b4 = new byte[4]; ++ int i = 0; ++ for( i = 0; i < 4; i++ ) ++ { ++ // Read four "meaningful" bytes: ++ int b = 0; ++ do{ b = in.read(); } ++ while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC ); ++ ++ if( b < 0 ) ++ break; // Reads a -1 if end of stream ++ ++ b4[i] = (byte)b; ++ } // end for: each needed input byte ++ ++ if( i == 4 ) ++ { ++ numSigBytes = decode4to3( b4, 0, buffer, 0, options ); ++ position = 0; ++ } // end if: got four characters ++ else if( i == 0 ){ ++ return -1; ++ } // end else if: also padded correctly ++ else ++ { ++ // Must have broken out from above. ++ throw new java.io.IOException( "Improperly padded Base64 input." ); ++ } // end ++ ++ } // end else: decode ++ } // end else: get data ++ ++ // Got data? ++ if( position >= 0 ) ++ { ++ // End of relevant data? ++ if( /*!encode &&*/ position >= numSigBytes ) ++ return -1; ++ ++ if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) ++ { ++ lineLength = 0; ++ return '\n'; ++ } // end if ++ else ++ { ++ lineLength++; // This isn't important when decoding ++ // but throwing an extra "if" seems ++ // just as wasteful. ++ ++ int b = buffer[ position++ ]; ++ ++ if( position >= bufferLength ) ++ position = -1; ++ ++ return b & 0xFF; // This is how you "cast" a byte that's ++ // intended to be unsigned. ++ } // end else ++ } // end if: position >= 0 ++ ++ // Else error ++ else ++ { ++ // When JDK1.4 is more accepted, use an assertion here. ++ throw new java.io.IOException( "Error in Base64 code reading stream." ); ++ } // end else ++ } // end read ++ ++ ++ /** ++ * Calls {@link #read()} repeatedly until the end of stream ++ * is reached or len bytes are read. ++ * Returns number of bytes read into array or -1 if ++ * end of stream is encountered. ++ * ++ * @param dest array to hold values ++ * @param off offset for array ++ * @param len max number of bytes to read into array ++ * @return bytes read into array or -1 if end of stream is encountered. ++ * @since 1.3 ++ */ ++ public int read( byte[] dest, int off, int len ) throws java.io.IOException ++ { ++ int i; ++ int b; ++ for( i = 0; i < len; i++ ) ++ { ++ b = read(); ++ ++ //if( b < 0 && i == 0 ) ++ // return -1; ++ ++ if( b >= 0 ) ++ dest[off + i] = (byte)b; ++ else if( i == 0 ) ++ return -1; ++ else ++ break; // Out of 'for' loop ++ } // end for: each byte read ++ return i; ++ } // end read ++ ++ } // end inner class InputStream ++ ++ ++ ++ ++ ++ ++ /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ ++ ++ ++ ++ /** ++ * A {@link Base64.OutputStream} will write data to another ++ * java.io.OutputStream, given in the constructor, ++ * and encode/decode to/from Base64 notation on the fly. ++ * ++ * @see Base64 ++ * @since 1.3 ++ */ ++ public static class OutputStream extends java.io.FilterOutputStream ++ { ++ private boolean encode; ++ private int position; ++ private byte[] buffer; ++ private int bufferLength; ++ private int lineLength; ++ private boolean breakLines; ++ private byte[] b4; // Scratch used in a few places ++ private boolean suspendEncoding; ++ private int options; // Record for later ++ private byte[] alphabet; // Local copies to avoid extra method calls ++ private byte[] decodabet; // Local copies to avoid extra method calls ++ ++ /** ++ * Constructs a {@link Base64.OutputStream} in ENCODE mode. ++ * ++ * @param out the java.io.OutputStream to which data will be written. ++ * @since 1.3 ++ */ ++ public OutputStream( java.io.OutputStream out ) ++ { ++ this( out, ENCODE ); ++ } // end constructor ++ ++ ++ /** ++ * Constructs a {@link Base64.OutputStream} in ++ * either ENCODE or DECODE mode. ++ *

      ++ * Valid options:

      ++         *   ENCODE or DECODE: Encode or Decode as data is read.
      ++         *   DONT_BREAK_LINES: don't break lines at 76 characters
      ++         *     (only meaningful when encoding)
      ++         *     Note: Technically, this makes your encoding non-compliant.
      ++         * 
      ++ *

      ++ * Example: new Base64.OutputStream( out, Base64.ENCODE ) ++ * ++ * @param out the java.io.OutputStream to which data will be written. ++ * @param options Specified options. ++ * @see Base64#ENCODE ++ * @see Base64#DECODE ++ * @see Base64#DONT_BREAK_LINES ++ * @since 1.3 ++ */ ++ public OutputStream( java.io.OutputStream out, int options ) ++ { ++ super( out ); ++ this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; ++ this.encode = (options & ENCODE) == ENCODE; ++ this.bufferLength = encode ? 3 : 4; ++ this.buffer = new byte[ bufferLength ]; ++ this.position = 0; ++ this.lineLength = 0; ++ this.suspendEncoding = false; ++ this.b4 = new byte[4]; ++ this.options = options; ++ this.alphabet = getAlphabet(options); ++ this.decodabet = getDecodabet(options); ++ } // end constructor ++ ++ ++ /** ++ * Writes the byte to the output stream after ++ * converting to/from Base64 notation. ++ * When encoding, bytes are buffered three ++ * at a time before the output stream actually ++ * gets a write() call. ++ * When decoding, bytes are buffered four ++ * at a time. ++ * ++ * @param theByte the byte to write ++ * @since 1.3 ++ */ ++ public void write(int theByte) throws java.io.IOException ++ { ++ // Encoding suspended? ++ if( suspendEncoding ) ++ { ++ super.out.write( theByte ); ++ return; ++ } // end if: supsended ++ ++ // Encode? ++ if( encode ) ++ { ++ buffer[ position++ ] = (byte)theByte; ++ if( position >= bufferLength ) // Enough to encode. ++ { ++ out.write( encode3to4( b4, buffer, bufferLength, options ) ); ++ ++ lineLength += 4; ++ if( breakLines && lineLength >= MAX_LINE_LENGTH ) ++ { ++ out.write( NEW_LINE ); ++ lineLength = 0; ++ } // end if: end of line ++ ++ position = 0; ++ } // end if: enough to output ++ } // end if: encoding ++ ++ // Else, Decoding ++ else ++ { ++ // Meaningful Base64 character? ++ if( decodabet[ theByte & 0x7f ] > WHITE_SPACE_ENC ) ++ { ++ buffer[ position++ ] = (byte)theByte; ++ if( position >= bufferLength ) // Enough to output. ++ { ++ int len = Base64.decode4to3( buffer, 0, b4, 0, options ); ++ out.write( b4, 0, len ); ++ //out.write( Base64.decode4to3( buffer ) ); ++ position = 0; ++ } // end if: enough to output ++ } // end if: meaningful base64 character ++ else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC ) ++ { ++ throw new java.io.IOException( "Invalid character in Base64 data." ); ++ } // end else: not white space either ++ } // end else: decoding ++ } // end write ++ ++ ++ ++ /** ++ * Calls {@link #write(int)} repeatedly until len ++ * bytes are written. ++ * ++ * @param theBytes array from which to read bytes ++ * @param off offset for array ++ * @param len max number of bytes to read into array ++ * @since 1.3 ++ */ ++ public void write( byte[] theBytes, int off, int len ) throws java.io.IOException ++ { ++ // Encoding suspended? ++ if( suspendEncoding ) ++ { ++ super.out.write( theBytes, off, len ); ++ return; ++ } // end if: supsended ++ ++ for( int i = 0; i < len; i++ ) ++ { ++ write( theBytes[ off + i ] ); ++ } // end for: each byte written ++ ++ } // end write ++ ++ ++ ++ /** ++ * Method added by PHIL. [Thanks, PHIL. -Rob] ++ * This pads the buffer without closing the stream. ++ */ ++ public void flushBase64() throws java.io.IOException ++ { ++ if( position > 0 ) ++ { ++ if( encode ) ++ { ++ out.write( encode3to4( b4, buffer, position, options ) ); ++ position = 0; ++ } // end if: encoding ++ else ++ { ++ throw new java.io.IOException( "Base64 input not properly padded." ); ++ } // end else: decoding ++ } // end if: buffer partially full ++ ++ } // end flush ++ ++ ++ /** ++ * Flushes and closes (I think, in the superclass) the stream. ++ * ++ * @since 1.3 ++ */ ++ public void close() throws java.io.IOException ++ { ++ // 1. Ensure that pending characters are written ++ flushBase64(); ++ ++ // 2. Actually close the stream ++ // Base class both flushes and closes. ++ super.close(); ++ ++ buffer = null; ++ out = null; ++ } // end close ++ ++ ++ ++ /** ++ * Suspends encoding of the stream. ++ * May be helpful if you need to embed a piece of ++ * base640-encoded data in a stream. ++ * ++ * @since 1.5.1 ++ */ ++ public void suspendEncoding() throws java.io.IOException ++ { ++ flushBase64(); ++ this.suspendEncoding = true; ++ } // end suspendEncoding ++ ++ ++ /** ++ * Resumes encoding of the stream. ++ * May be helpful if you need to embed a piece of ++ * base640-encoded data in a stream. ++ * ++ * @since 1.5.1 ++ */ ++ public void resumeEncoding() ++ { ++ this.suspendEncoding = false; ++ } // end resumeEncoding ++ ++ ++ ++ } // end inner class OutputStream ++ ++ ++} // end class Base64 ++ +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/Cache.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/Cache.java +new file mode 100644 +index 0000000..069b430 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/Cache.java +@@ -0,0 +1,678 @@ ++/** ++ * $Revision: 1456 $ ++ * $Date: 2005-06-01 22:04:54 -0700 (Wed, 01 Jun 2005) $ ++ * ++ * Copyright 2003-2005 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.util.collections.AbstractMapEntry; ++ ++import java.util.*; ++ ++/** ++ * A specialized Map that is size-limited (using an LRU algorithm) and ++ * has an optional expiration time for cache items. The Map is thread-safe.

      ++ * ++ * The algorithm for cache is as follows: a HashMap is maintained for fast ++ * object lookup. Two linked lists are maintained: one keeps objects in the ++ * order they are accessed from cache, the other keeps objects in the order ++ * they were originally added to cache. When objects are added to cache, they ++ * are first wrapped by a CacheObject which maintains the following pieces ++ * of information:

        ++ *
      • A pointer to the node in the linked list that maintains accessed ++ * order for the object. Keeping a reference to the node lets us avoid ++ * linear scans of the linked list. ++ *
      • A pointer to the node in the linked list that maintains the age ++ * of the object in cache. Keeping a reference to the node lets us avoid ++ * linear scans of the linked list.
      ++ *

      ++ * To get an object from cache, a hash lookup is performed to get a reference ++ * to the CacheObject that wraps the real object we are looking for. ++ * The object is subsequently moved to the front of the accessed linked list ++ * and any necessary cache cleanups are performed. Cache deletion and expiration ++ * is performed as needed. ++ * ++ * @author Matt Tucker ++ */ ++public class Cache implements Map { ++ ++ /** ++ * The map the keys and values are stored in. ++ */ ++ protected Map> map; ++ ++ /** ++ * Linked list to maintain order that cache objects are accessed ++ * in, most used to least used. ++ */ ++ protected LinkedList lastAccessedList; ++ ++ /** ++ * Linked list to maintain time that cache objects were initially added ++ * to the cache, most recently added to oldest added. ++ */ ++ protected LinkedList ageList; ++ ++ /** ++ * Maximum number of items the cache will hold. ++ */ ++ protected int maxCacheSize; ++ ++ /** ++ * Maximum length of time objects can exist in cache before expiring. ++ */ ++ protected long maxLifetime; ++ ++ /** ++ * Maintain the number of cache hits and misses. A cache hit occurs every ++ * time the get method is called and the cache contains the requested ++ * object. A cache miss represents the opposite occurence.

      ++ * ++ * Keeping track of cache hits and misses lets one measure how efficient ++ * the cache is; the higher the percentage of hits, the more efficient. ++ */ ++ protected long cacheHits, cacheMisses = 0L; ++ ++ /** ++ * Create a new cache and specify the maximum size of for the cache in ++ * bytes, and the maximum lifetime of objects. ++ * ++ * @param maxSize the maximum number of objects the cache will hold. -1 ++ * means the cache has no max size. ++ * @param maxLifetime the maximum amount of time (in ms) objects can exist in ++ * cache before being deleted. -1 means objects never expire. ++ */ ++ public Cache(int maxSize, long maxLifetime) { ++ if (maxSize == 0) { ++ throw new IllegalArgumentException("Max cache size cannot be 0."); ++ } ++ this.maxCacheSize = maxSize; ++ this.maxLifetime = maxLifetime; ++ ++ // Our primary data structure is a hash map. The default capacity of 11 ++ // is too small in almost all cases, so we set it bigger. ++ map = new HashMap>(103); ++ ++ lastAccessedList = new LinkedList(); ++ ageList = new LinkedList(); ++ } ++ ++ public synchronized V put(K key, V value) { ++ V oldValue = null; ++ // Delete an old entry if it exists. ++ if (map.containsKey(key)) { ++ oldValue = remove(key, true); ++ } ++ ++ CacheObject cacheObject = new CacheObject(value); ++ map.put(key, cacheObject); ++ // Make an entry into the cache order list. ++ // Store the cache order list entry so that we can get back to it ++ // during later lookups. ++ cacheObject.lastAccessedListNode = lastAccessedList.addFirst(key); ++ // Add the object to the age list ++ LinkedListNode ageNode = ageList.addFirst(key); ++ ageNode.timestamp = System.currentTimeMillis(); ++ cacheObject.ageListNode = ageNode; ++ ++ // If cache is too full, remove least used cache entries until it is not too full. ++ cullCache(); ++ ++ return oldValue; ++ } ++ ++ public synchronized V get(Object key) { ++ // First, clear all entries that have been in cache longer than the ++ // maximum defined age. ++ deleteExpiredEntries(); ++ ++ CacheObject cacheObject = map.get(key); ++ if (cacheObject == null) { ++ // The object didn't exist in cache, so increment cache misses. ++ cacheMisses++; ++ return null; ++ } ++ // Remove the object from it's current place in the cache order list, ++ // and re-insert it at the front of the list. ++ cacheObject.lastAccessedListNode.remove(); ++ lastAccessedList.addFirst(cacheObject.lastAccessedListNode); ++ ++ // The object exists in cache, so increment cache hits. Also, increment ++ // the object's read count. ++ cacheHits++; ++ cacheObject.readCount++; ++ ++ return cacheObject.object; ++ } ++ ++ public synchronized V remove(Object key) { ++ return remove(key, false); ++ } ++ ++ /* ++ * Remove operation with a flag so we can tell coherence if the remove was ++ * caused by cache internal processing such as eviction or loading ++ */ ++ public synchronized V remove(Object key, boolean internal) { ++ //noinspection SuspiciousMethodCalls ++ CacheObject cacheObject = map.remove(key); ++ // If the object is not in cache, stop trying to remove it. ++ if (cacheObject == null) { ++ return null; ++ } ++ // Remove from the cache order list ++ cacheObject.lastAccessedListNode.remove(); ++ cacheObject.ageListNode.remove(); ++ // Remove references to linked list nodes ++ cacheObject.ageListNode = null; ++ cacheObject.lastAccessedListNode = null; ++ ++ return cacheObject.object; ++ } ++ ++ public synchronized void clear() { ++ Object[] keys = map.keySet().toArray(); ++ for (Object key : keys) { ++ remove(key); ++ } ++ ++ // Now, reset all containers. ++ map.clear(); ++ lastAccessedList.clear(); ++ ageList.clear(); ++ ++ cacheHits = 0; ++ cacheMisses = 0; ++ } ++ ++ public synchronized int size() { ++ // First, clear all entries that have been in cache longer than the ++ // maximum defined age. ++ deleteExpiredEntries(); ++ ++ return map.size(); ++ } ++ ++ public synchronized boolean isEmpty() { ++ // First, clear all entries that have been in cache longer than the ++ // maximum defined age. ++ deleteExpiredEntries(); ++ ++ return map.isEmpty(); ++ } ++ ++ public synchronized Collection values() { ++ // First, clear all entries that have been in cache longer than the ++ // maximum defined age. ++ deleteExpiredEntries(); ++ ++ return Collections.unmodifiableCollection(new AbstractCollection() { ++ Collection> values = map.values(); ++ public Iterator iterator() { ++ return new Iterator() { ++ Iterator> it = values.iterator(); ++ ++ public boolean hasNext() { ++ return it.hasNext(); ++ } ++ ++ public V next() { ++ return it.next().object; ++ } ++ ++ public void remove() { ++ it.remove(); ++ } ++ }; ++ } ++ ++ public int size() { ++ return values.size(); ++ } ++ }); ++ } ++ ++ public synchronized boolean containsKey(Object key) { ++ // First, clear all entries that have been in cache longer than the ++ // maximum defined age. ++ deleteExpiredEntries(); ++ ++ return map.containsKey(key); ++ } ++ ++ public void putAll(Map map) { ++ for (Entry entry : map.entrySet()) { ++ V value = entry.getValue(); ++ // If the map is another DefaultCache instance than the ++ // entry values will be CacheObject instances that need ++ // to be converted to the normal object form. ++ if (value instanceof CacheObject) { ++ //noinspection unchecked ++ value = ((CacheObject) value).object; ++ } ++ put(entry.getKey(), value); ++ } ++ } ++ ++ public synchronized boolean containsValue(Object value) { ++ // First, clear all entries that have been in cache longer than the ++ // maximum defined age. ++ deleteExpiredEntries(); ++ ++ //noinspection unchecked ++ CacheObject cacheObject = new CacheObject((V) value); ++ ++ return map.containsValue(cacheObject); ++ } ++ ++ public synchronized Set> entrySet() { ++ // Warning -- this method returns CacheObject instances and not Objects ++ // in the same form they were put into cache. ++ ++ // First, clear all entries that have been in cache longer than the ++ // maximum defined age. ++ deleteExpiredEntries(); ++ ++ return new AbstractSet>() { ++ private final Set>> set = map.entrySet(); ++ ++ public Iterator> iterator() { ++ return new Iterator>() { ++ private final Iterator>> it = set.iterator(); ++ public boolean hasNext() { ++ return it.hasNext(); ++ } ++ ++ public Entry next() { ++ Map.Entry> entry = it.next(); ++ return new AbstractMapEntry(entry.getKey(), entry.getValue().object) { ++ @Override ++ public V setValue(V value) { ++ throw new UnsupportedOperationException("Cannot set"); ++ } ++ }; ++ } ++ ++ public void remove() { ++ it.remove(); ++ } ++ }; ++ ++ } ++ ++ public int size() { ++ return set.size(); ++ } ++ }; ++ } ++ ++ public synchronized Set keySet() { ++ // First, clear all entries that have been in cache longer than the ++ // maximum defined age. ++ deleteExpiredEntries(); ++ ++ return Collections.unmodifiableSet(map.keySet()); ++ } ++ ++ public long getCacheHits() { ++ return cacheHits; ++ } ++ ++ public long getCacheMisses() { ++ return cacheMisses; ++ } ++ ++ public int getMaxCacheSize() { ++ return maxCacheSize; ++ } ++ ++ public synchronized void setMaxCacheSize(int maxCacheSize) { ++ this.maxCacheSize = maxCacheSize; ++ // It's possible that the new max size is smaller than our current cache ++ // size. If so, we need to delete infrequently used items. ++ cullCache(); ++ } ++ ++ public long getMaxLifetime() { ++ return maxLifetime; ++ } ++ ++ public void setMaxLifetime(long maxLifetime) { ++ this.maxLifetime = maxLifetime; ++ } ++ ++ /** ++ * Clears all entries out of cache where the entries are older than the ++ * maximum defined age. ++ */ ++ protected synchronized void deleteExpiredEntries() { ++ // Check if expiration is turned on. ++ if (maxLifetime <= 0) { ++ return; ++ } ++ ++ // Remove all old entries. To do this, we remove objects from the end ++ // of the linked list until they are no longer too old. We get to avoid ++ // any hash lookups or looking at any more objects than is strictly ++ // neccessary. ++ LinkedListNode node = ageList.getLast(); ++ // If there are no entries in the age list, return. ++ if (node == null) { ++ return; ++ } ++ ++ // Determine the expireTime, which is the moment in time that elements ++ // should expire from cache. Then, we can do an easy check to see ++ // if the expire time is greater than the expire time. ++ long expireTime = System.currentTimeMillis() - maxLifetime; ++ ++ while (expireTime > node.timestamp) { ++ if (remove(node.object, true) == null) { ++ System.err.println("Error attempting to remove(" + node.object.toString() + ++ ") - cacheObject not found in cache!"); ++ // remove from the ageList ++ node.remove(); ++ } ++ ++ // Get the next node. ++ node = ageList.getLast(); ++ // If there are no more entries in the age list, return. ++ if (node == null) { ++ return; ++ } ++ } ++ } ++ ++ /** ++ * Removes the least recently used elements if the cache size is greater than ++ * or equal to the maximum allowed size until the cache is at least 10% empty. ++ */ ++ protected synchronized void cullCache() { ++ // Check if a max cache size is defined. ++ if (maxCacheSize < 0) { ++ return; ++ } ++ ++ // See if the cache is too big. If so, clean out cache until it's 10% free. ++ if (map.size() > maxCacheSize) { ++ // First, delete any old entries to see how much memory that frees. ++ deleteExpiredEntries(); ++ // Next, delete the least recently used elements until 10% of the cache ++ // has been freed. ++ int desiredSize = (int) (maxCacheSize * .90); ++ for (int i=map.size(); i>desiredSize; i--) { ++ // Get the key and invoke the remove method on it. ++ if (remove(lastAccessedList.getLast().object, true) == null) { ++ System.err.println("Error attempting to cullCache with remove(" + ++ lastAccessedList.getLast().object.toString() + ") - " + ++ "cacheObject not found in cache!"); ++ lastAccessedList.getLast().remove(); ++ } ++ } ++ } ++ } ++ ++ /** ++ * Wrapper for all objects put into cache. It's primary purpose is to maintain ++ * references to the linked lists that maintain the creation time of the object ++ * and the ordering of the most used objects. ++ * ++ * This class is optimized for speed rather than strictly correct encapsulation. ++ */ ++ private static class CacheObject { ++ ++ /** ++ * Underlying object wrapped by the CacheObject. ++ */ ++ public V object; ++ ++ /** ++ * A reference to the node in the cache order list. We keep the reference ++ * here to avoid linear scans of the list. Every time the object is ++ * accessed, the node is removed from its current spot in the list and ++ * moved to the front. ++ */ ++ public LinkedListNode lastAccessedListNode; ++ ++ /** ++ * A reference to the node in the age order list. We keep the reference ++ * here to avoid linear scans of the list. The reference is used if the ++ * object has to be deleted from the list. ++ */ ++ public LinkedListNode ageListNode; ++ ++ /** ++ * A count of the number of times the object has been read from cache. ++ */ ++ public int readCount = 0; ++ ++ /** ++ * Creates a new cache object wrapper. ++ * ++ * @param object the underlying Object to wrap. ++ */ ++ public CacheObject(V object) { ++ this.object = object; ++ } ++ ++ public boolean equals(Object o) { ++ if (this == o) { ++ return true; ++ } ++ if (!(o instanceof CacheObject)) { ++ return false; ++ } ++ ++ final CacheObject cacheObject = (CacheObject) o; ++ ++ return object.equals(cacheObject.object); ++ ++ } ++ ++ public int hashCode() { ++ return object.hashCode(); ++ } ++ } ++ ++ /** ++ * Simple LinkedList implementation. The main feature is that list nodes ++ * are public, which allows very fast delete operations when one has a ++ * reference to the node that is to be deleted.

      ++ */ ++ private static class LinkedList { ++ ++ /** ++ * The root of the list keeps a reference to both the first and last ++ * elements of the list. ++ */ ++ private LinkedListNode head = new LinkedListNode("head", null, null); ++ ++ /** ++ * Creates a new linked list. ++ */ ++ public LinkedList() { ++ head.next = head.previous = head; ++ } ++ ++ /** ++ * Returns the first linked list node in the list. ++ * ++ * @return the first element of the list. ++ */ ++ public LinkedListNode getFirst() { ++ LinkedListNode node = head.next; ++ if (node == head) { ++ return null; ++ } ++ return node; ++ } ++ ++ /** ++ * Returns the last linked list node in the list. ++ * ++ * @return the last element of the list. ++ */ ++ public LinkedListNode getLast() { ++ LinkedListNode node = head.previous; ++ if (node == head) { ++ return null; ++ } ++ return node; ++ } ++ ++ /** ++ * Adds a node to the beginning of the list. ++ * ++ * @param node the node to add to the beginning of the list. ++ * @return the node ++ */ ++ public LinkedListNode addFirst(LinkedListNode node) { ++ node.next = head.next; ++ node.previous = head; ++ node.previous.next = node; ++ node.next.previous = node; ++ return node; ++ } ++ ++ /** ++ * Adds an object to the beginning of the list by automatically creating a ++ * a new node and adding it to the beginning of the list. ++ * ++ * @param object the object to add to the beginning of the list. ++ * @return the node created to wrap the object. ++ */ ++ public LinkedListNode addFirst(Object object) { ++ LinkedListNode node = new LinkedListNode(object, head.next, head); ++ node.previous.next = node; ++ node.next.previous = node; ++ return node; ++ } ++ ++ /** ++ * Adds an object to the end of the list by automatically creating a ++ * a new node and adding it to the end of the list. ++ * ++ * @param object the object to add to the end of the list. ++ * @return the node created to wrap the object. ++ */ ++ public LinkedListNode addLast(Object object) { ++ LinkedListNode node = new LinkedListNode(object, head, head.previous); ++ node.previous.next = node; ++ node.next.previous = node; ++ return node; ++ } ++ ++ /** ++ * Erases all elements in the list and re-initializes it. ++ */ ++ public void clear() { ++ //Remove all references in the list. ++ LinkedListNode node = getLast(); ++ while (node != null) { ++ node.remove(); ++ node = getLast(); ++ } ++ ++ //Re-initialize. ++ head.next = head.previous = head; ++ } ++ ++ /** ++ * Returns a String representation of the linked list with a comma ++ * delimited list of all the elements in the list. ++ * ++ * @return a String representation of the LinkedList. ++ */ ++ public String toString() { ++ LinkedListNode node = head.next; ++ StringBuilder buf = new StringBuilder(); ++ while (node != head) { ++ buf.append(node.toString()).append(", "); ++ node = node.next; ++ } ++ return buf.toString(); ++ } ++ } ++ ++ /** ++ * Doubly linked node in a LinkedList. Most LinkedList implementations keep the ++ * equivalent of this class private. We make it public so that references ++ * to each node in the list can be maintained externally. ++ * ++ * Exposing this class lets us make remove operations very fast. Remove is ++ * built into this class and only requires two reference reassignments. If ++ * remove existed in the main LinkedList class, a linear scan would have to ++ * be performed to find the correct node to delete. ++ * ++ * The linked list implementation was specifically written for the Jive ++ * cache system. While it can be used as a general purpose linked list, for ++ * most applications, it is more suitable to use the linked list that is part ++ * of the Java Collections package. ++ */ ++ private static class LinkedListNode { ++ ++ public LinkedListNode previous; ++ public LinkedListNode next; ++ public Object object; ++ ++ /** ++ * This class is further customized for the Jive cache system. It ++ * maintains a timestamp of when a Cacheable object was first added to ++ * cache. Timestamps are stored as long values and represent the number ++ * of milliseconds passed since January 1, 1970 00:00:00.000 GMT.

      ++ * ++ * The creation timestamp is used in the case that the cache has a ++ * maximum lifetime set. In that case, when ++ * [current time] - [creation time] > [max lifetime], the object will be ++ * deleted from cache. ++ */ ++ public long timestamp; ++ ++ /** ++ * Constructs a new linked list node. ++ * ++ * @param object the Object that the node represents. ++ * @param next a reference to the next LinkedListNode in the list. ++ * @param previous a reference to the previous LinkedListNode in the list. ++ */ ++ public LinkedListNode(Object object, LinkedListNode next, ++ LinkedListNode previous) ++ { ++ this.object = object; ++ this.next = next; ++ this.previous = previous; ++ } ++ ++ /** ++ * Removes this node from the linked list that it is a part of. ++ */ ++ public void remove() { ++ previous.next = next; ++ next.previous = previous; ++ } ++ ++ /** ++ * Returns a String representation of the linked list node by calling the ++ * toString method of the node's object. ++ * ++ * @return a String representation of the LinkedListNode. ++ */ ++ public String toString() { ++ return object.toString(); ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/DNSUtil.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/DNSUtil.java +new file mode 100644 +index 0000000..2765a19 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/DNSUtil.java +@@ -0,0 +1,234 @@ ++/** ++ * $Revision: 1456 $ ++ * $Date: 2005-06-01 22:04:54 -0700 (Wed, 01 Jun 2005) $ ++ * ++ * Copyright 2003-2005 Jive Software. ++ * ++ * All rights reserved. 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.Map; ++ ++import org.xbill.DNS.Lookup; ++import org.xbill.DNS.Record; ++import org.xbill.DNS.SRVRecord; ++import org.xbill.DNS.TextParseException; ++import org.xbill.DNS.Type; ++ ++/** ++ * Utilty class to perform DNS lookups for XMPP services. ++ * ++ * @author Matt Tucker ++ */ ++public class DNSUtil { ++ ++ /** ++ * Create a cache to hold the 100 most recently accessed DNS lookups for a ++ * period of 10 minutes. ++ */ ++ @SuppressWarnings("unchecked") ++ private static Map ccache = new Cache(100, 1000 * 60 * 10); ++ @SuppressWarnings("unchecked") ++ private static Map scache = new Cache(100, 1000 * 60 * 10); ++ ++ private static HostAddress resolveSRV(String domain) { ++ String bestHost = null; ++ int bestPort = -1; ++ int bestPriority = Integer.MAX_VALUE; ++ int bestWeight = 0; ++ Lookup lookup; ++ try { ++ lookup = new Lookup(domain, Type.SRV); ++ Record recs[] = lookup.run(); ++ if (recs == null) { return null; } ++ for (Record rec : recs) { ++ SRVRecord record = (SRVRecord) rec; ++ if (record != null && record.getTarget() != null) { ++ int weight = (int) (record.getWeight() * record.getWeight() * Math ++ .random()); ++ if (record.getPriority() < bestPriority) { ++ bestPriority = record.getPriority(); ++ bestWeight = weight; ++ bestHost = record.getTarget().toString(); ++ bestPort = record.getPort(); ++ } else if (record.getPriority() == bestPriority) { ++ if (weight > bestWeight) { ++ bestPriority = record.getPriority(); ++ bestWeight = weight; ++ bestHost = record.getTarget().toString(); ++ bestPort = record.getPort(); ++ } ++ } ++ } ++ } ++ } catch (TextParseException e) { ++ } catch (NullPointerException e) { ++ } ++ if (bestHost == null) { ++ return null; ++ } ++ // Host entries in DNS should end with a ".". ++ if (bestHost.endsWith(".")) { ++ bestHost = bestHost.substring(0, bestHost.length() - 1); ++ } ++ return new HostAddress(bestHost, bestPort); ++ } ++ ++ /** ++ * Returns the host name and port that the specified XMPP server can be ++ * reached at for client-to-server communication. A DNS lookup for a SRV ++ * record in the form "_xmpp-client._tcp.example.com" is attempted, ++ * according to section 14.4 of RFC 3920. If that lookup fails, a lookup in ++ * the older form of "_jabber._tcp.example.com" is attempted since servers ++ * that implement an older version of the protocol may be listed using that ++ * notation. If that lookup fails as well, it's assumed that the XMPP server ++ * lives at the host resolved by a DNS lookup at the specified domain on the ++ * default port of 5222. ++ *

      ++ * ++ * As an example, a lookup for "example.com" may return ++ * "im.example.com:5269". ++ * ++ * Note on SRV record selection. We now check priority and weight, but we ++ * still don't do this correctly. The missing behavior is this: if we fail ++ * to reach a host based on its SRV record then we need to select another ++ * host from the other SRV records. In Smack 3.1.1 we're not going to be ++ * able to do the major system redesign to correct this. ++ * ++ * @param domain ++ * the domain. ++ * @return a HostAddress, which encompasses the hostname and port that the ++ * XMPP server can be reached at for the specified domain. ++ */ ++ public static HostAddress resolveXMPPDomain(String domain) { ++ // Return item from cache if it exists. ++ synchronized (ccache) { ++ if (ccache.containsKey(domain)) { ++ HostAddress address = (HostAddress) ccache.get(domain); ++ if (address != null) { ++ return address; ++ } ++ } ++ } ++ HostAddress result = resolveSRV("_xmpp-client._tcp." + domain); ++ if (result == null) { ++ result = resolveSRV("_jabber._tcp." + domain); ++ } ++ if (result == null) { ++ result = new HostAddress(domain, 5222); ++ } ++ // Add item to cache. ++ synchronized (ccache) { ++ ccache.put(domain, result); ++ } ++ return result; ++ } ++ ++ /** ++ * Returns the host name and port that the specified XMPP server can be ++ * reached at for server-to-server communication. A DNS lookup for a SRV ++ * record in the form "_xmpp-server._tcp.example.com" is attempted, ++ * according to section 14.4 of RFC 3920. If that lookup fails, a lookup in ++ * the older form of "_jabber._tcp.example.com" is attempted since servers ++ * that implement an older version of the protocol may be listed using that ++ * notation. If that lookup fails as well, it's assumed that the XMPP server ++ * lives at the host resolved by a DNS lookup at the specified domain on the ++ * default port of 5269. ++ *

      ++ * ++ * As an example, a lookup for "example.com" may return ++ * "im.example.com:5269". ++ * ++ * @param domain ++ * the domain. ++ * @return a HostAddress, which encompasses the hostname and port that the ++ * XMPP server can be reached at for the specified domain. ++ */ ++ public static HostAddress resolveXMPPServerDomain(String domain) { ++ // Return item from cache if it exists. ++ synchronized (scache) { ++ if (scache.containsKey(domain)) { ++ HostAddress address = (HostAddress) scache.get(domain); ++ if (address != null) { ++ return address; ++ } ++ } ++ } ++ HostAddress result = resolveSRV("_xmpp-server._tcp." + domain); ++ if (result == null) { ++ result = resolveSRV("_jabber._tcp." + domain); ++ } ++ if (result == null) { ++ result = new HostAddress(domain, 5269); ++ } ++ // Add item to cache. ++ synchronized (scache) { ++ scache.put(domain, result); ++ } ++ return result; ++ } ++ ++ /** ++ * Encapsulates a hostname and port. ++ */ ++ public static class HostAddress { ++ ++ private String host; ++ private int port; ++ ++ private HostAddress(String host, int port) { ++ this.host = host; ++ this.port = port; ++ } ++ ++ /** ++ * Returns the hostname. ++ * ++ * @return the hostname. ++ */ ++ public String getHost() { ++ return host; ++ } ++ ++ /** ++ * Returns the port. ++ * ++ * @return the port. ++ */ ++ public int getPort() { ++ return port; ++ } ++ ++ public String toString() { ++ return host + ":" + port; ++ } ++ ++ public boolean equals(Object o) { ++ if (this == o) { ++ return true; ++ } ++ if (!(o instanceof HostAddress)) { ++ return false; ++ } ++ ++ final HostAddress address = (HostAddress) o; ++ ++ if (!host.equals(address.host)) { ++ return false; ++ } ++ return port == address.port; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/ObservableReader.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/ObservableReader.java +new file mode 100644 +index 0000000..8100927 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/ObservableReader.java +@@ -0,0 +1,118 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.*; ++import java.util.*; ++ ++/** ++ * An ObservableReader is a wrapper on a Reader that notifies to its listeners when ++ * reading character streams. ++ * ++ * @author Gaston Dombiak ++ */ ++public class ObservableReader extends Reader { ++ ++ Reader wrappedReader = null; ++ List listeners = new ArrayList(); ++ ++ public ObservableReader(Reader wrappedReader) { ++ this.wrappedReader = wrappedReader; ++ } ++ ++ public int read(char[] cbuf, int off, int len) throws IOException { ++ int count = wrappedReader.read(cbuf, off, len); ++ if (count > 0) { ++ String str = new String(cbuf, off, count); ++ // Notify that a new string has been read ++ ReaderListener[] readerListeners = null; ++ synchronized (listeners) { ++ readerListeners = new ReaderListener[listeners.size()]; ++ listeners.toArray(readerListeners); ++ } ++ for (int i = 0; i < readerListeners.length; i++) { ++ readerListeners[i].read(str); ++ } ++ } ++ return count; ++ } ++ ++ public void close() throws IOException { ++ wrappedReader.close(); ++ } ++ ++ public int read() throws IOException { ++ return wrappedReader.read(); ++ } ++ ++ public int read(char cbuf[]) throws IOException { ++ return wrappedReader.read(cbuf); ++ } ++ ++ public long skip(long n) throws IOException { ++ return wrappedReader.skip(n); ++ } ++ ++ public boolean ready() throws IOException { ++ return wrappedReader.ready(); ++ } ++ ++ public boolean markSupported() { ++ return wrappedReader.markSupported(); ++ } ++ ++ public void mark(int readAheadLimit) throws IOException { ++ wrappedReader.mark(readAheadLimit); ++ } ++ ++ public void reset() throws IOException { ++ wrappedReader.reset(); ++ } ++ ++ /** ++ * Adds a reader listener to this reader that will be notified when ++ * new strings are read. ++ * ++ * @param readerListener a reader listener. ++ */ ++ public void addReaderListener(ReaderListener readerListener) { ++ if (readerListener == null) { ++ return; ++ } ++ synchronized (listeners) { ++ if (!listeners.contains(readerListener)) { ++ listeners.add(readerListener); ++ } ++ } ++ } ++ ++ /** ++ * Removes a reader listener from this reader. ++ * ++ * @param readerListener a reader listener. ++ */ ++ public void removeReaderListener(ReaderListener readerListener) { ++ synchronized (listeners) { ++ listeners.remove(readerListener); ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/ObservableWriter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/ObservableWriter.java +new file mode 100644 +index 0000000..5da46aa +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/ObservableWriter.java +@@ -0,0 +1,120 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.*; ++import java.util.*; ++ ++/** ++ * An ObservableWriter is a wrapper on a Writer that notifies to its listeners when ++ * writing to character streams. ++ * ++ * @author Gaston Dombiak ++ */ ++public class ObservableWriter extends Writer { ++ ++ Writer wrappedWriter = null; ++ List listeners = new ArrayList(); ++ ++ public ObservableWriter(Writer wrappedWriter) { ++ this.wrappedWriter = wrappedWriter; ++ } ++ ++ public void write(char cbuf[], int off, int len) throws IOException { ++ wrappedWriter.write(cbuf, off, len); ++ String str = new String(cbuf, off, len); ++ notifyListeners(str); ++ } ++ ++ public void flush() throws IOException { ++ wrappedWriter.flush(); ++ } ++ ++ public void close() throws IOException { ++ wrappedWriter.close(); ++ } ++ ++ public void write(int c) throws IOException { ++ wrappedWriter.write(c); ++ } ++ ++ public void write(char cbuf[]) throws IOException { ++ wrappedWriter.write(cbuf); ++ String str = new String(cbuf); ++ notifyListeners(str); ++ } ++ ++ public void write(String str) throws IOException { ++ wrappedWriter.write(str); ++ notifyListeners(str); ++ } ++ ++ public void write(String str, int off, int len) throws IOException { ++ wrappedWriter.write(str, off, len); ++ str = str.substring(off, off + len); ++ notifyListeners(str); ++ } ++ ++ /** ++ * Notify that a new string has been written. ++ * ++ * @param str the written String to notify ++ */ ++ private void notifyListeners(String str) { ++ WriterListener[] writerListeners = null; ++ synchronized (listeners) { ++ writerListeners = new WriterListener[listeners.size()]; ++ listeners.toArray(writerListeners); ++ } ++ for (int i = 0; i < writerListeners.length; i++) { ++ writerListeners[i].write(str); ++ } ++ } ++ ++ /** ++ * Adds a writer listener to this writer that will be notified when ++ * new strings are sent. ++ * ++ * @param writerListener a writer listener. ++ */ ++ public void addWriterListener(WriterListener writerListener) { ++ if (writerListener == null) { ++ return; ++ } ++ synchronized (listeners) { ++ if (!listeners.contains(writerListener)) { ++ listeners.add(writerListener); ++ } ++ } ++ } ++ ++ /** ++ * Removes a writer listener from this writer. ++ * ++ * @param writerListener a writer listener. ++ */ ++ public void removeWriterListener(WriterListener writerListener) { ++ synchronized (listeners) { ++ listeners.remove(writerListener); ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/PacketParserUtils.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/PacketParserUtils.java +new file mode 100644 +index 0000000..a2eccb5 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/PacketParserUtils.java +@@ -0,0 +1,885 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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 org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.packet.*; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.jivesoftware.smack.provider.ProviderManager; ++import org.jivesoftware.smack.sasl.SASLMechanism.Failure; ++import org.xmlpull.v1.XmlPullParser; ++import org.xmlpull.v1.XmlPullParserException; ++ ++import java.io.ByteArrayInputStream; ++import java.io.IOException; ++import java.io.ObjectInputStream; ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++ ++/** ++ * Utility class that helps to parse packets. Any parsing packets method that must be shared ++ * between many clients must be placed in this utility class. ++ * ++ * @author Gaston Dombiak ++ */ ++public class PacketParserUtils { ++ ++ /** ++ * Namespace used to store packet properties. ++ */ ++ private static final String PROPERTIES_NAMESPACE = ++ "http://www.jivesoftware.com/xmlns/xmpp/properties"; ++ ++ /** ++ * Parses a message packet. ++ * ++ * @param parser the XML parser, positioned at the start of a message packet. ++ * @return a Message packet. ++ * @throws Exception if an exception occurs while parsing the packet. ++ */ ++ public static Packet parseMessage(XmlPullParser parser) throws Exception { ++ Message message = new Message(); ++ String id = parser.getAttributeValue("", "id"); ++ message.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id); ++ message.setTo(parser.getAttributeValue("", "to")); ++ message.setFrom(parser.getAttributeValue("", "from")); ++ message.setType(Message.Type.fromString(parser.getAttributeValue("", "type"))); ++ String language = getLanguageAttribute(parser); ++ ++ // determine message's default language ++ String defaultLanguage = null; ++ if (language != null && !"".equals(language.trim())) { ++ message.setLanguage(language); ++ defaultLanguage = language; ++ } ++ else { ++ defaultLanguage = Packet.getDefaultLanguage(); ++ } ++ ++ // Parse sub-elements. We include extra logic to make sure the values ++ // are only read once. This is because it's possible for the names to appear ++ // in arbitrary sub-elements. ++ boolean done = false; ++ String thread = null; ++ Map properties = null; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ String elementName = parser.getName(); ++ String namespace = parser.getNamespace(); ++ if (elementName.equals("subject")) { ++ String xmlLang = getLanguageAttribute(parser); ++ if (xmlLang == null) { ++ xmlLang = defaultLanguage; ++ } ++ ++ String subject = parseContent(parser); ++ ++ if (message.getSubject(xmlLang) == null) { ++ message.addSubject(xmlLang, subject); ++ } ++ } ++ else if (elementName.equals("body")) { ++ String xmlLang = getLanguageAttribute(parser); ++ if (xmlLang == null) { ++ xmlLang = defaultLanguage; ++ } ++ ++ String body = parseContent(parser); ++ ++ if (message.getBody(xmlLang) == null) { ++ message.addBody(xmlLang, body); ++ } ++ } ++ else if (elementName.equals("thread")) { ++ if (thread == null) { ++ thread = parser.nextText(); ++ } ++ } ++ else if (elementName.equals("error")) { ++ message.setError(parseError(parser)); ++ } ++ else if (elementName.equals("properties") && ++ namespace.equals(PROPERTIES_NAMESPACE)) ++ { ++ properties = parseProperties(parser); ++ } ++ // Otherwise, it must be a packet extension. ++ else { ++ message.addExtension( ++ PacketParserUtils.parsePacketExtension(elementName, namespace, parser)); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("message")) { ++ done = true; ++ } ++ } ++ } ++ ++ message.setThread(thread); ++ // Set packet properties. ++ if (properties != null) { ++ for (String name : properties.keySet()) { ++ message.setProperty(name, properties.get(name)); ++ } ++ } ++ return message; ++ } ++ ++ /** ++ * Returns the content of a tag as string regardless of any tags included. ++ * ++ * @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) ++ throws XmlPullParserException, IOException { ++ String content = ""; ++ int parserDepth = parser.getDepth(); ++ while (!(parser.next() == XmlPullParser.END_TAG && parser ++ .getDepth() == parserDepth)) { ++ content += parser.getText(); ++ } ++ return content; ++ } ++ ++ /** ++ * Parses a presence packet. ++ * ++ * @param parser the XML parser, positioned at the start of a presence packet. ++ * @return a Presence packet. ++ * @throws Exception if an exception occurs while parsing the packet. ++ */ ++ public static Presence parsePresence(XmlPullParser parser) throws Exception { ++ Presence.Type type = Presence.Type.available; ++ String typeString = parser.getAttributeValue("", "type"); ++ if (typeString != null && !typeString.equals("")) { ++ try { ++ type = Presence.Type.valueOf(typeString); ++ } ++ catch (IllegalArgumentException iae) { ++ System.err.println("Found invalid presence type " + typeString); ++ } ++ } ++ Presence presence = new Presence(type); ++ presence.setTo(parser.getAttributeValue("", "to")); ++ presence.setFrom(parser.getAttributeValue("", "from")); ++ String id = parser.getAttributeValue("", "id"); ++ presence.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id); ++ ++ String language = getLanguageAttribute(parser); ++ if (language != null && !"".equals(language.trim())) { ++ presence.setLanguage(language); ++ } ++ presence.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id); ++ ++ // Parse sub-elements ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ String elementName = parser.getName(); ++ String namespace = parser.getNamespace(); ++ if (elementName.equals("status")) { ++ presence.setStatus(parser.nextText()); ++ } ++ else if (elementName.equals("priority")) { ++ try { ++ int priority = Integer.parseInt(parser.nextText()); ++ presence.setPriority(priority); ++ } ++ catch (NumberFormatException nfe) { ++ // Ignore. ++ } ++ catch (IllegalArgumentException iae) { ++ // Presence priority is out of range so assume priority to be zero ++ presence.setPriority(0); ++ } ++ } ++ else if (elementName.equals("show")) { ++ String modeText = parser.nextText(); ++ try { ++ presence.setMode(Presence.Mode.valueOf(modeText)); ++ } ++ catch (IllegalArgumentException iae) { ++ System.err.println("Found invalid presence mode " + modeText); ++ } ++ } ++ else if (elementName.equals("error")) { ++ presence.setError(parseError(parser)); ++ } ++ else if (elementName.equals("properties") && ++ namespace.equals(PROPERTIES_NAMESPACE)) ++ { ++ Map properties = parseProperties(parser); ++ // Set packet properties. ++ for (String name : properties.keySet()) { ++ presence.setProperty(name, properties.get(name)); ++ } ++ } ++ // Otherwise, it must be a packet extension. ++ else { ++ presence.addExtension( ++ PacketParserUtils.parsePacketExtension(elementName, namespace, parser)); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("presence")) { ++ done = true; ++ } ++ } ++ } ++ return presence; ++ } ++ ++ /** ++ * Parses an IQ packet. ++ * ++ * @param parser the XML parser, positioned at the start of an IQ packet. ++ * @return an IQ object. ++ * @throws Exception if an exception occurs while parsing the packet. ++ */ ++ public static IQ parseIQ(XmlPullParser parser, Connection connection) throws Exception { ++ IQ iqPacket = null; ++ ++ String id = parser.getAttributeValue("", "id"); ++ String to = parser.getAttributeValue("", "to"); ++ String from = parser.getAttributeValue("", "from"); ++ IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type")); ++ XMPPError error = null; ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ ++ if (eventType == XmlPullParser.START_TAG) { ++ String elementName = parser.getName(); ++ String namespace = parser.getNamespace(); ++ if (elementName.equals("error")) { ++ error = PacketParserUtils.parseError(parser); ++ } ++ else if (elementName.equals("query") && namespace.equals("jabber:iq:auth")) { ++ iqPacket = parseAuthentication(parser); ++ } ++ else if (elementName.equals("query") && namespace.equals("jabber:iq:roster")) { ++ iqPacket = parseRoster(parser); ++ } ++ else if (elementName.equals("query") && namespace.equals("jabber:iq:register")) { ++ iqPacket = parseRegistration(parser); ++ } ++ else if (elementName.equals("bind") && ++ namespace.equals("urn:ietf:params:xml:ns:xmpp-bind")) { ++ iqPacket = parseResourceBinding(parser); ++ } ++ // Otherwise, see if there is a registered provider for ++ // this element name and namespace. ++ else { ++ Object provider = ProviderManager.getInstance().getIQProvider(elementName, namespace); ++ if (provider != null) { ++ if (provider instanceof IQProvider) { ++ iqPacket = ((IQProvider)provider).parseIQ(parser); ++ } ++ else if (provider instanceof Class) { ++ iqPacket = (IQ)PacketParserUtils.parseWithIntrospection(elementName, ++ (Class)provider, parser); ++ } ++ } ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("iq")) { ++ done = true; ++ } ++ } ++ } ++ // Decide what to do when an IQ packet was not understood ++ if (iqPacket == null) { ++ if (IQ.Type.GET == type || IQ.Type.SET == type ) { ++ // If the IQ stanza is of type "get" or "set" containing a child element ++ // qualified by a namespace it does not understand, then answer an IQ of ++ // type "error" with code 501 ("feature-not-implemented") ++ iqPacket = new IQ() { ++ public String getChildElementXML() { ++ return null; ++ } ++ }; ++ iqPacket.setPacketID(id); ++ iqPacket.setTo(from); ++ iqPacket.setFrom(to); ++ iqPacket.setType(IQ.Type.ERROR); ++ iqPacket.setError(new XMPPError(XMPPError.Condition.feature_not_implemented)); ++ connection.sendPacket(iqPacket); ++ return null; ++ } ++ else { ++ // If an IQ packet wasn't created above, create an empty IQ packet. ++ iqPacket = new IQ() { ++ public String getChildElementXML() { ++ return null; ++ } ++ }; ++ } ++ } ++ ++ // Set basic values on the iq packet. ++ iqPacket.setPacketID(id); ++ iqPacket.setTo(to); ++ iqPacket.setFrom(from); ++ iqPacket.setType(type); ++ iqPacket.setError(error); ++ ++ return iqPacket; ++ } ++ ++ private static Authentication parseAuthentication(XmlPullParser parser) throws Exception { ++ Authentication authentication = new Authentication(); ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("username")) { ++ authentication.setUsername(parser.nextText()); ++ } ++ else if (parser.getName().equals("password")) { ++ authentication.setPassword(parser.nextText()); ++ } ++ else if (parser.getName().equals("digest")) { ++ authentication.setDigest(parser.nextText()); ++ } ++ else if (parser.getName().equals("resource")) { ++ authentication.setResource(parser.nextText()); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("query")) { ++ done = true; ++ } ++ } ++ } ++ return authentication; ++ } ++ ++ private static RosterPacket parseRoster(XmlPullParser parser) throws Exception { ++ RosterPacket roster = new RosterPacket(); ++ boolean done = false; ++ RosterPacket.Item item = null; ++ while (!done) { ++ if(parser.getEventType()==XmlPullParser.START_TAG && ++ parser.getName().equals("query")){ ++ String version = parser.getAttributeValue(null, "ver"); ++ roster.setVersion(version); ++ } ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("item")) { ++ String jid = parser.getAttributeValue("", "jid"); ++ String name = parser.getAttributeValue("", "name"); ++ // Create packet. ++ item = new RosterPacket.Item(jid, name); ++ // Set status. ++ String ask = parser.getAttributeValue("", "ask"); ++ RosterPacket.ItemStatus status = RosterPacket.ItemStatus.fromString(ask); ++ item.setItemStatus(status); ++ // Set type. ++ String subscription = parser.getAttributeValue("", "subscription"); ++ RosterPacket.ItemType type = RosterPacket.ItemType.valueOf(subscription != null ? subscription : "none"); ++ item.setItemType(type); ++ } ++ if (parser.getName().equals("group") && item!= null) { ++ final String groupName = parser.nextText(); ++ if (groupName != null && groupName.trim().length() > 0) { ++ item.addGroupName(groupName); ++ } ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("item")) { ++ roster.addRosterItem(item); ++ } ++ if (parser.getName().equals("query")) { ++ done = true; ++ } ++ } ++ } ++ return roster; ++ } ++ ++ private static Registration parseRegistration(XmlPullParser parser) throws Exception { ++ Registration registration = new Registration(); ++ Map fields = null; ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ // Any element that's in the jabber:iq:register namespace, ++ // attempt to parse it if it's in the form value. ++ if (parser.getNamespace().equals("jabber:iq:register")) { ++ String name = parser.getName(); ++ String value = ""; ++ if (fields == null) { ++ fields = new HashMap(); ++ } ++ ++ if (parser.next() == XmlPullParser.TEXT) { ++ value = parser.getText(); ++ } ++ // Ignore instructions, but anything else should be added to the map. ++ if (!name.equals("instructions")) { ++ fields.put(name, value); ++ } ++ else { ++ registration.setInstructions(value); ++ } ++ } ++ // Otherwise, it must be a packet extension. ++ else { ++ registration.addExtension( ++ PacketParserUtils.parsePacketExtension( ++ parser.getName(), ++ parser.getNamespace(), ++ parser)); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("query")) { ++ done = true; ++ } ++ } ++ } ++ registration.setAttributes(fields); ++ return registration; ++ } ++ ++ private static Bind parseResourceBinding(XmlPullParser parser) throws IOException, ++ XmlPullParserException { ++ Bind bind = new Bind(); ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("resource")) { ++ bind.setResource(parser.nextText()); ++ } ++ else if (parser.getName().equals("jid")) { ++ bind.setJid(parser.nextText()); ++ } ++ } else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("bind")) { ++ done = true; ++ } ++ } ++ } ++ ++ return bind; ++ } ++ ++ /** ++ * Parse the available SASL mechanisms reported from the server. ++ * ++ * @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 Exception if an exception occurs while parsing the stanza. ++ */ ++ public static Collection parseMechanisms(XmlPullParser parser) throws Exception { ++ List mechanisms = new ArrayList(); ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ ++ if (eventType == XmlPullParser.START_TAG) { ++ String elementName = parser.getName(); ++ if (elementName.equals("mechanism")) { ++ mechanisms.add(parser.nextText()); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("mechanisms")) { ++ done = true; ++ } ++ } ++ } ++ return mechanisms; ++ } ++ ++ /** ++ * Parse the available compression methods reported from the server. ++ * ++ * @param parser the XML parser, positioned at the start of the compression stanza. ++ * @return a collection of Stings with the methods included in the compression stanza. ++ * @throws Exception if an exception occurs while parsing the stanza. ++ */ ++ public static Collection parseCompressionMethods(XmlPullParser parser) ++ throws IOException, XmlPullParserException { ++ List methods = new ArrayList(); ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ ++ if (eventType == XmlPullParser.START_TAG) { ++ String elementName = parser.getName(); ++ if (elementName.equals("method")) { ++ methods.add(parser.nextText()); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("compression")) { ++ done = true; ++ } ++ } ++ } ++ return methods; ++ } ++ ++ /** ++ * Parse a properties sub-packet. If any errors occur while de-serializing Java object ++ * properties, an exception will be printed and not thrown since a thrown ++ * exception will shut down the entire connection. ClassCastExceptions will occur ++ * when both the sender and receiver of the packet don't have identical versions ++ * of the same class. ++ * ++ * @param parser the XML parser, positioned at the start of a properties sub-packet. ++ * @return a map of the properties. ++ * @throws Exception if an error occurs while parsing the properties. ++ */ ++ public static Map parseProperties(XmlPullParser parser) throws Exception { ++ Map properties = new HashMap(); ++ while (true) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG && parser.getName().equals("property")) { ++ // Parse a property ++ boolean done = false; ++ String name = null; ++ String type = null; ++ String valueText = null; ++ Object value = null; ++ while (!done) { ++ eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ String elementName = parser.getName(); ++ if (elementName.equals("name")) { ++ name = parser.nextText(); ++ } ++ else if (elementName.equals("value")) { ++ type = parser.getAttributeValue("", "type"); ++ valueText = parser.nextText(); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("property")) { ++ if ("integer".equals(type)) { ++ value = Integer.valueOf(valueText); ++ } ++ else if ("long".equals(type)) { ++ value = Long.valueOf(valueText); ++ } ++ else if ("float".equals(type)) { ++ value = Float.valueOf(valueText); ++ } ++ else if ("double".equals(type)) { ++ value = Double.valueOf(valueText); ++ } ++ else if ("boolean".equals(type)) { ++ value = Boolean.valueOf(valueText); ++ } ++ else if ("string".equals(type)) { ++ value = valueText; ++ } ++ else if ("java-object".equals(type)) { ++ try { ++ byte [] bytes = StringUtils.decodeBase64(valueText); ++ ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes)); ++ value = in.readObject(); ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } ++ if (name != null && value != null) { ++ properties.put(name, value); ++ } ++ done = true; ++ } ++ } ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("properties")) { ++ break; ++ } ++ } ++ } ++ return properties; ++ } ++ ++ /** ++ * Parses SASL authentication error packets. ++ * ++ * @param parser the XML parser. ++ * @return a SASL Failure packet. ++ * @throws Exception if an exception occurs while parsing the packet. ++ */ ++ public static Failure parseSASLFailure(XmlPullParser parser) throws Exception { ++ String condition = null; ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ ++ if (eventType == XmlPullParser.START_TAG) { ++ if (!parser.getName().equals("failure")) { ++ condition = parser.getName(); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("failure")) { ++ done = true; ++ } ++ } ++ } ++ return new Failure(condition); ++ } ++ ++ /** ++ * Parses stream error packets. ++ * ++ *

      If there are multiple stream errors, ignores "text" ones. ++ * ++ * @param parser the XML parser. ++ * @return a stream error packet. ++ * @throws Exception if an exception occurs while parsing the packet. ++ */ ++ public static StreamError parseStreamError(XmlPullParser parser) throws IOException, ++ XmlPullParserException { ++ StreamError streamError = null; ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ ++ if (eventType == XmlPullParser.START_TAG && ++ (streamError == null || "text".equals(streamError.getCode())) ++ ) { ++ streamError = new StreamError(parser.getName()); ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("error")) { ++ done = true; ++ } ++ } ++ } ++ return streamError; ++} ++ ++ /** ++ * Parses error sub-packets. ++ * ++ * @param parser the XML parser. ++ * @return an error sub-packet. ++ * @throws Exception if an exception occurs while parsing the packet. ++ */ ++ public static XMPPError parseError(XmlPullParser parser) throws Exception { ++ final String errorNamespace = "urn:ietf:params:xml:ns:xmpp-stanzas"; ++ String errorCode = "-1"; ++ String type = null; ++ String message = null; ++ String condition = null; ++ List extensions = new ArrayList(); ++ ++ // Parse the error header ++ for (int i=0; i properties = null; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ String elementName = parser.getName(); ++ String namespace = parser.getNamespace(); ++ if (elementName.equals("subject")) { ++ String xmlLang = getLanguageAttribute(parser); ++ if (xmlLang == null) { ++ xmlLang = defaultLanguage; ++ } ++ ++ String subject = parseContent(parser); ++ ++ if (message.getSubject(xmlLang) == null) { ++ message.addSubject(xmlLang, subject); ++ } ++ } ++ else if (elementName.equals("body")) { ++ String xmlLang = getLanguageAttribute(parser); ++ if (xmlLang == null) { ++ xmlLang = defaultLanguage; ++ } ++ ++ String body = parseContent(parser); ++ ++ if (message.getBody(xmlLang) == null) { ++ message.addBody(xmlLang, body); ++ } ++ } ++ else if (elementName.equals("thread")) { ++ if (thread == null) { ++ thread = parser.nextText(); ++ } ++ } ++ else if (elementName.equals("error")) { ++ message.setError(parseError(parser)); ++ } ++ else if (elementName.equals("properties") && ++ namespace.equals(PROPERTIES_NAMESPACE)) ++ { ++ properties = parseProperties(parser); ++ } ++ // Otherwise, it must be a packet extension. ++ else { ++ message.addExtension( ++ PacketParserUtils.parsePacketExtension(elementName, namespace, parser)); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("message")) { ++ done = true; ++ } ++ } ++ } ++ ++ message.setThread(thread); ++ // Set packet properties. ++ if (properties != null) { ++ for (String name : properties.keySet()) { ++ message.setProperty(name, properties.get(name)); ++ } ++ } ++ return message; ++ } ++ ++ /** ++ * Returns the content of a tag as string regardless of any tags included. ++ * ++ * @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) ++ throws XmlPullParserException, IOException { ++ String content = ""; ++ int parserDepth = parser.getDepth(); ++ while (!(parser.next() == XmlPullParser.END_TAG && parser ++ .getDepth() == parserDepth)) { ++ content += parser.getText(); ++ } ++ return content; ++ } ++ ++ /** ++ * Parses a presence packet. ++ * ++ * @param parser the XML parser, positioned at the start of a presence packet. ++ * @return a Presence packet. ++ * @throws Exception if an exception occurs while parsing the packet. ++ */ ++ public static Presence parsePresence(XmlPullParser parser) throws Exception { ++ Presence.Type type = Presence.Type.available; ++ String typeString = parser.getAttributeValue("", "type"); ++ if (typeString != null && !typeString.equals("")) { ++ try { ++ type = Presence.Type.valueOf(typeString); ++ } ++ catch (IllegalArgumentException iae) { ++ System.err.println("Found invalid presence type " + typeString); ++ } ++ } ++ Presence presence = new Presence(type); ++ presence.setTo(parser.getAttributeValue("", "to")); ++ presence.setFrom(parser.getAttributeValue("", "from")); ++ String id = parser.getAttributeValue("", "id"); ++ presence.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id); ++ ++ String language = getLanguageAttribute(parser); ++ if (language != null && !"".equals(language.trim())) { ++ presence.setLanguage(language); ++ } ++ presence.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id); ++ ++ // Parse sub-elements ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ String elementName = parser.getName(); ++ String namespace = parser.getNamespace(); ++ if (elementName.equals("status")) { ++ presence.setStatus(parser.nextText()); ++ } ++ else if (elementName.equals("priority")) { ++ try { ++ int priority = Integer.parseInt(parser.nextText()); ++ presence.setPriority(priority); ++ } ++ catch (NumberFormatException nfe) { ++ // Ignore. ++ } ++ catch (IllegalArgumentException iae) { ++ // Presence priority is out of range so assume priority to be zero ++ presence.setPriority(0); ++ } ++ } ++ else if (elementName.equals("show")) { ++ String modeText = parser.nextText(); ++ try { ++ presence.setMode(Presence.Mode.valueOf(modeText)); ++ } ++ catch (IllegalArgumentException iae) { ++ System.err.println("Found invalid presence mode " + modeText); ++ } ++ } ++ else if (elementName.equals("error")) { ++ presence.setError(parseError(parser)); ++ } ++ else if (elementName.equals("properties") && ++ namespace.equals(PROPERTIES_NAMESPACE)) ++ { ++ Map properties = parseProperties(parser); ++ // Set packet properties. ++ for (String name : properties.keySet()) { ++ presence.setProperty(name, properties.get(name)); ++ } ++ } ++ // Otherwise, it must be a packet extension. ++ else { ++ presence.addExtension( ++ PacketParserUtils.parsePacketExtension(elementName, namespace, parser)); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("presence")) { ++ done = true; ++ } ++ } ++ } ++ return presence; ++ } ++ ++ /** ++ * Parses an IQ packet. ++ * ++ * @param parser the XML parser, positioned at the start of an IQ packet. ++ * @return an IQ object. ++ * @throws Exception if an exception occurs while parsing the packet. ++ */ ++ public static IQ parseIQ(XmlPullParser parser, Connection connection) throws Exception { ++ IQ iqPacket = null; ++ ++ String id = parser.getAttributeValue("", "id"); ++ String to = parser.getAttributeValue("", "to"); ++ String from = parser.getAttributeValue("", "from"); ++ IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type")); ++ XMPPError error = null; ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ ++ if (eventType == XmlPullParser.START_TAG) { ++ String elementName = parser.getName(); ++ String namespace = parser.getNamespace(); ++ if (elementName.equals("error")) { ++ error = PacketParserUtils.parseError(parser); ++ } ++ else if (elementName.equals("query") && namespace.equals("jabber:iq:auth")) { ++ iqPacket = parseAuthentication(parser); ++ } ++ else if (elementName.equals("query") && namespace.equals("jabber:iq:roster")) { ++ iqPacket = parseRoster(parser); ++ } ++ else if (elementName.equals("query") && namespace.equals("jabber:iq:register")) { ++ iqPacket = parseRegistration(parser); ++ } ++ else if (elementName.equals("bind") && ++ namespace.equals("urn:ietf:params:xml:ns:xmpp-bind")) { ++ iqPacket = parseResourceBinding(parser); ++ } ++ // Otherwise, see if there is a registered provider for ++ // this element name and namespace. ++ else { ++ Object provider = ProviderManager.getInstance().getIQProvider(elementName, namespace); ++ if (provider != null) { ++ if (provider instanceof IQProvider) { ++ iqPacket = ((IQProvider)provider).parseIQ(parser); ++ } ++ else if (provider instanceof Class) { ++ iqPacket = (IQ)PacketParserUtils.parseWithIntrospection(elementName, ++ (Class)provider, parser); ++ } ++ } ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("iq")) { ++ done = true; ++ } ++ } ++ } ++ // Decide what to do when an IQ packet was not understood ++ if (iqPacket == null) { ++ if (IQ.Type.GET == type || IQ.Type.SET == type ) { ++ // If the IQ stanza is of type "get" or "set" containing a child element ++ // qualified by a namespace it does not understand, then answer an IQ of ++ // type "error" with code 501 ("feature-not-implemented") ++ iqPacket = new IQ() { ++ public String getChildElementXML() { ++ return null; ++ } ++ }; ++ iqPacket.setPacketID(id); ++ iqPacket.setTo(from); ++ iqPacket.setFrom(to); ++ iqPacket.setType(IQ.Type.ERROR); ++ iqPacket.setError(new XMPPError(XMPPError.Condition.feature_not_implemented)); ++ connection.sendPacket(iqPacket); ++ return null; ++ } ++ else { ++ // If an IQ packet wasn't created above, create an empty IQ packet. ++ iqPacket = new IQ() { ++ public String getChildElementXML() { ++ return null; ++ } ++ }; ++ } ++ } ++ ++ // Set basic values on the iq packet. ++ iqPacket.setPacketID(id); ++ iqPacket.setTo(to); ++ iqPacket.setFrom(from); ++ iqPacket.setType(type); ++ iqPacket.setError(error); ++ ++ return iqPacket; ++ } ++ ++ private static Authentication parseAuthentication(XmlPullParser parser) throws Exception { ++ Authentication authentication = new Authentication(); ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("username")) { ++ authentication.setUsername(parser.nextText()); ++ } ++ else if (parser.getName().equals("password")) { ++ authentication.setPassword(parser.nextText()); ++ } ++ else if (parser.getName().equals("digest")) { ++ authentication.setDigest(parser.nextText()); ++ } ++ else if (parser.getName().equals("resource")) { ++ authentication.setResource(parser.nextText()); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("query")) { ++ done = true; ++ } ++ } ++ } ++ return authentication; ++ } ++ ++ private static RosterPacket parseRoster(XmlPullParser parser) throws Exception { ++ RosterPacket roster = new RosterPacket(); ++ boolean done = false; ++ RosterPacket.Item item = null; ++ while (!done) { ++ if(parser.getEventType()==XmlPullParser.START_TAG && ++ parser.getName().equals("query")){ ++ String version = parser.getAttributeValue(null, "ver"); ++ roster.setVersion(version); ++ } ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("item")) { ++ String jid = parser.getAttributeValue("", "jid"); ++ String name = parser.getAttributeValue("", "name"); ++ // Create packet. ++ item = new RosterPacket.Item(jid, name); ++ // Set status. ++ String ask = parser.getAttributeValue("", "ask"); ++ RosterPacket.ItemStatus status = RosterPacket.ItemStatus.fromString(ask); ++ item.setItemStatus(status); ++ // Set type. ++ String subscription = parser.getAttributeValue("", "subscription"); ++ RosterPacket.ItemType type = RosterPacket.ItemType.valueOf(subscription != null ? subscription : "none"); ++ item.setItemType(type); ++ } ++ if (parser.getName().equals("group") && item!= null) { ++ final String groupName = parser.nextText(); ++ if (groupName != null && groupName.trim().length() > 0) { ++ item.addGroupName(groupName); ++ } ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("item")) { ++ roster.addRosterItem(item); ++ } ++ if (parser.getName().equals("query")) { ++ done = true; ++ } ++ } ++ } ++ return roster; ++ } ++ ++ private static Registration parseRegistration(XmlPullParser parser) throws Exception { ++ Registration registration = new Registration(); ++ Map fields = null; ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ // Any element that's in the jabber:iq:register namespace, ++ // attempt to parse it if it's in the form value. ++ if (parser.getNamespace().equals("jabber:iq:register")) { ++ String name = parser.getName(); ++ String value = ""; ++ if (fields == null) { ++ fields = new HashMap(); ++ } ++ ++ if (parser.next() == XmlPullParser.TEXT) { ++ value = parser.getText(); ++ } ++ // Ignore instructions, but anything else should be added to the map. ++ if (!name.equals("instructions")) { ++ fields.put(name, value); ++ } ++ else { ++ registration.setInstructions(value); ++ } ++ } ++ // Otherwise, it must be a packet extension. ++ else { ++ registration.addExtension( ++ PacketParserUtils.parsePacketExtension( ++ parser.getName(), ++ parser.getNamespace(), ++ parser)); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("query")) { ++ done = true; ++ } ++ } ++ } ++ registration.setAttributes(fields); ++ return registration; ++ } ++ ++ private static Bind parseResourceBinding(XmlPullParser parser) throws IOException, ++ XmlPullParserException { ++ Bind bind = new Bind(); ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("resource")) { ++ bind.setResource(parser.nextText()); ++ } ++ else if (parser.getName().equals("jid")) { ++ bind.setJid(parser.nextText()); ++ } ++ } else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("bind")) { ++ done = true; ++ } ++ } ++ } ++ ++ return bind; ++ } ++ ++ /** ++ * Parse the available SASL mechanisms reported from the server. ++ * ++ * @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 Exception if an exception occurs while parsing the stanza. ++ */ ++ public static Collection parseMechanisms(XmlPullParser parser) throws Exception { ++ List mechanisms = new ArrayList(); ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ ++ if (eventType == XmlPullParser.START_TAG) { ++ String elementName = parser.getName(); ++ if (elementName.equals("mechanism")) { ++ mechanisms.add(parser.nextText()); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("mechanisms")) { ++ done = true; ++ } ++ } ++ } ++ return mechanisms; ++ } ++ ++ /** ++ * Parse the available compression methods reported from the server. ++ * ++ * @param parser the XML parser, positioned at the start of the compression stanza. ++ * @return a collection of Stings with the methods included in the compression stanza. ++ * @throws Exception if an exception occurs while parsing the stanza. ++ */ ++ public static Collection parseCompressionMethods(XmlPullParser parser) ++ throws IOException, XmlPullParserException { ++ List methods = new ArrayList(); ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ ++ if (eventType == XmlPullParser.START_TAG) { ++ String elementName = parser.getName(); ++ if (elementName.equals("method")) { ++ methods.add(parser.nextText()); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("compression")) { ++ done = true; ++ } ++ } ++ } ++ return methods; ++ } ++ ++ /** ++ * Parse a properties sub-packet. If any errors occur while de-serializing Java object ++ * properties, an exception will be printed and not thrown since a thrown ++ * exception will shut down the entire connection. ClassCastExceptions will occur ++ * when both the sender and receiver of the packet don't have identical versions ++ * of the same class. ++ * ++ * @param parser the XML parser, positioned at the start of a properties sub-packet. ++ * @return a map of the properties. ++ * @throws Exception if an error occurs while parsing the properties. ++ */ ++ public static Map parseProperties(XmlPullParser parser) throws Exception { ++ Map properties = new HashMap(); ++ while (true) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG && parser.getName().equals("property")) { ++ // Parse a property ++ boolean done = false; ++ String name = null; ++ String type = null; ++ String valueText = null; ++ Object value = null; ++ while (!done) { ++ eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ String elementName = parser.getName(); ++ if (elementName.equals("name")) { ++ name = parser.nextText(); ++ } ++ else if (elementName.equals("value")) { ++ type = parser.getAttributeValue("", "type"); ++ valueText = parser.nextText(); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("property")) { ++ if ("integer".equals(type)) { ++ value = Integer.valueOf(valueText); ++ } ++ else if ("long".equals(type)) { ++ value = Long.valueOf(valueText); ++ } ++ else if ("float".equals(type)) { ++ value = Float.valueOf(valueText); ++ } ++ else if ("double".equals(type)) { ++ value = Double.valueOf(valueText); ++ } ++ else if ("boolean".equals(type)) { ++ value = Boolean.valueOf(valueText); ++ } ++ else if ("string".equals(type)) { ++ value = valueText; ++ } ++ else if ("java-object".equals(type)) { ++ try { ++ byte [] bytes = StringUtils.decodeBase64(valueText); ++ ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes)); ++ value = in.readObject(); ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } ++ if (name != null && value != null) { ++ properties.put(name, value); ++ } ++ done = true; ++ } ++ } ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("properties")) { ++ break; ++ } ++ } ++ } ++ return properties; ++ } ++ ++ /** ++ * Parses SASL authentication error packets. ++ * ++ * @param parser the XML parser. ++ * @return a SASL Failure packet. ++ * @throws Exception if an exception occurs while parsing the packet. ++ */ ++ public static Failure parseSASLFailure(XmlPullParser parser) throws Exception { ++ String condition = null; ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ ++ if (eventType == XmlPullParser.START_TAG) { ++ if (!parser.getName().equals("failure")) { ++ condition = parser.getName(); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("failure")) { ++ done = true; ++ } ++ } ++ } ++ return new Failure(condition); ++ } ++ ++ /** ++ * Parses stream error packets. ++ * ++ *

      If there are multiple stream errors, ignores "text" ones. ++ * ++ * @param parser the XML parser. ++ * @return a stream error packet. ++ * @throws Exception if an exception occurs while parsing the packet. ++ */ ++ public static StreamError parseStreamError(XmlPullParser parser) throws IOException, ++ XmlPullParserException { ++ StreamError streamError = null; ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ ++ if (eventType == XmlPullParser.START_TAG && ++ (streamError == null || "text".equals(streamError.getCode())) ++ ) { ++ streamError = new StreamError(parser.getName()); ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("error")) { ++ done = true; ++ } ++ } ++ } ++ return streamError; ++} ++ ++ /** ++ * Parses error sub-packets. ++ * ++ * @param parser the XML parser. ++ * @return an error sub-packet. ++ * @throws Exception if an exception occurs while parsing the packet. ++ */ ++ public static XMPPError parseError(XmlPullParser parser) throws Exception { ++ final String errorNamespace = "urn:ietf:params:xml:ns:xmpp-stanzas"; ++ String errorCode = "-1"; ++ String type = null; ++ String message = null; ++ String condition = null; ++ List extensions = new ArrayList(); ++ ++ // Parse the error header ++ for (int i=0; i ++ * Date formats are not synchronized. Since multiple threads access the format concurrently, it ++ * must be synchronized externally or you can use the convenience methods ++ * {@link #parseXEP0082Date(String)} and {@link #formatXEP0082Date(Date)}. ++ */ ++ public static final DateFormat XEP_0082_UTC_FORMAT = new SimpleDateFormat( ++ "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); ++ static { ++ XEP_0082_UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); ++ } ++ ++ private static final char[] QUOTE_ENCODE = """.toCharArray(); ++ private static final char[] APOS_ENCODE = "'".toCharArray(); ++ private static final char[] AMP_ENCODE = "&".toCharArray(); ++ private static final char[] LT_ENCODE = "<".toCharArray(); ++ private static final char[] GT_ENCODE = ">".toCharArray(); ++ ++ /** ++ * Parses the given date string in the XEP-0082 - XMPP Date and Time Profiles format. ++ * ++ * @param dateString the date string to parse ++ * @return the parsed Date ++ * @throws ParseException if the specified string cannot be parsed ++ */ ++ public static Date parseXEP0082Date(String dateString) throws ParseException { ++ synchronized (XEP_0082_UTC_FORMAT) { ++ return XEP_0082_UTC_FORMAT.parse(dateString); ++ } ++ } ++ ++ /** ++ * Formats a Date into a XEP-0082 - XMPP Date and Time Profiles string. ++ * ++ * @param date the time value to be formatted into a time string ++ * @return the formatted time string in XEP-0082 format ++ */ ++ public static String formatXEP0082Date(Date date) { ++ synchronized (XEP_0082_UTC_FORMAT) { ++ return XEP_0082_UTC_FORMAT.format(date); ++ } ++ } ++ ++ /** ++ * Returns the name portion of a XMPP address. For example, for the ++ * address "matt@jivesoftware.com/Smack", "matt" would be returned. If no ++ * username is present in the address, the empty string will be returned. ++ * ++ * @param XMPPAddress the XMPP address. ++ * @return the name portion of the XMPP address. ++ */ ++ public static String parseName(String XMPPAddress) { ++ if (XMPPAddress == null) { ++ return null; ++ } ++ int atIndex = XMPPAddress.lastIndexOf("@"); ++ if (atIndex <= 0) { ++ return ""; ++ } ++ else { ++ return XMPPAddress.substring(0, atIndex); ++ } ++ } ++ ++ /** ++ * Returns the server portion of a XMPP address. For example, for the ++ * address "matt@jivesoftware.com/Smack", "jivesoftware.com" would be returned. ++ * If no server is present in the address, the empty string will be returned. ++ * ++ * @param XMPPAddress the XMPP address. ++ * @return the server portion of the XMPP address. ++ */ ++ public static String parseServer(String XMPPAddress) { ++ if (XMPPAddress == null) { ++ return null; ++ } ++ int atIndex = XMPPAddress.lastIndexOf("@"); ++ // If the String ends with '@', return the empty string. ++ if (atIndex + 1 > XMPPAddress.length()) { ++ return ""; ++ } ++ int slashIndex = XMPPAddress.indexOf("/"); ++ if (slashIndex > 0 && slashIndex > atIndex) { ++ return XMPPAddress.substring(atIndex + 1, slashIndex); ++ } ++ else { ++ return XMPPAddress.substring(atIndex + 1); ++ } ++ } ++ ++ /** ++ * Returns the resource portion of a XMPP address. For example, for the ++ * address "matt@jivesoftware.com/Smack", "Smack" would be returned. If no ++ * resource is present in the address, the empty string will be returned. ++ * ++ * @param XMPPAddress the XMPP address. ++ * @return the resource portion of the XMPP address. ++ */ ++ public static String parseResource(String XMPPAddress) { ++ if (XMPPAddress == null) { ++ return null; ++ } ++ int slashIndex = XMPPAddress.indexOf("/"); ++ if (slashIndex + 1 > XMPPAddress.length() || slashIndex < 0) { ++ return ""; ++ } ++ else { ++ return XMPPAddress.substring(slashIndex + 1); ++ } ++ } ++ ++ /** ++ * Returns the XMPP address with any resource information removed. For example, ++ * for the address "matt@jivesoftware.com/Smack", "matt@jivesoftware.com" would ++ * be returned. ++ * ++ * @param XMPPAddress the XMPP address. ++ * @return the bare XMPP address without resource information. ++ */ ++ public static String parseBareAddress(String XMPPAddress) { ++ if (XMPPAddress == null) { ++ return null; ++ } ++ int slashIndex = XMPPAddress.indexOf("/"); ++ if (slashIndex < 0) { ++ return XMPPAddress; ++ } ++ else if (slashIndex == 0) { ++ return ""; ++ } ++ else { ++ return XMPPAddress.substring(0, slashIndex); ++ } ++ } ++ ++ /** ++ * Escapes the node portion of a JID according to "JID Escaping" (JEP-0106). ++ * Escaping replaces characters prohibited by node-prep with escape sequences, ++ * as follows:

      ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ *
      Unescaped CharacterEncoded Sequence
      <space>\20
      "\22
      &\26
      '\27
      /\2f
      :\3a
      <\3c
      >\3e
      @\40
      \\5c

      ++ * ++ * This process is useful when the node comes from an external source that doesn't ++ * conform to nodeprep. For example, a username in LDAP may be "Joe Smith". Because ++ * the <space> character isn't a valid part of a node, the username should ++ * be escaped to "Joe\20Smith" before being made into a JID (e.g. "joe\20smith@example.com" ++ * after case-folding, etc. has been applied).

      ++ * ++ * All node escaping and un-escaping must be performed manually at the appropriate ++ * time; the JID class will not escape or un-escape automatically. ++ * ++ * @param node the node. ++ * @return the escaped version of the node. ++ */ ++ public static String escapeNode(String node) { ++ if (node == null) { ++ return null; ++ } ++ StringBuilder buf = new StringBuilder(node.length() + 8); ++ for (int i=0, n=node.length(); i': buf.append("\\3e"); break; ++ case '@': buf.append("\\40"); break; ++ case '\\': buf.append("\\5c"); break; ++ default: { ++ if (Character.isWhitespace(c)) { ++ buf.append("\\20"); ++ } ++ else { ++ buf.append(c); ++ } ++ } ++ } ++ } ++ return buf.toString(); ++ } ++ ++ /** ++ * Un-escapes the node portion of a JID according to "JID Escaping" (JEP-0106).

      ++ * Escaping replaces characters prohibited by node-prep with escape sequences, ++ * as follows:

      ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ *
      Unescaped CharacterEncoded Sequence
      <space>\20
      "\22
      &\26
      '\27
      /\2f
      :\3a
      <\3c
      >\3e
      @\40
      \\5c

      ++ * ++ * This process is useful when the node comes from an external source that doesn't ++ * conform to nodeprep. For example, a username in LDAP may be "Joe Smith". Because ++ * the <space> character isn't a valid part of a node, the username should ++ * be escaped to "Joe\20Smith" before being made into a JID (e.g. "joe\20smith@example.com" ++ * after case-folding, etc. has been applied).

      ++ * ++ * All node escaping and un-escaping must be performed manually at the appropriate ++ * time; the JID class will not escape or un-escape automatically. ++ * ++ * @param node the escaped version of the node. ++ * @return the un-escaped version of the node. ++ */ ++ public static String unescapeNode(String node) { ++ if (node == null) { ++ return null; ++ } ++ char [] nodeChars = node.toCharArray(); ++ StringBuilder buf = new StringBuilder(nodeChars.length); ++ for (int i=0, n=nodeChars.length; i'); i+=2; break compare; ++ } ++ } ++ else if (c2 == '4') { ++ if (c3 == '0') { ++ buf.append("@"); ++ i+=2; ++ break compare; ++ } ++ } ++ else if (c2 == '5') { ++ if (c3 == 'c') { ++ buf.append("\\"); ++ i+=2; ++ break compare; ++ } ++ } ++ } ++ buf.append(c); ++ } ++ } ++ return buf.toString(); ++ } ++ ++ /** ++ * Escapes all necessary characters in the String so that it can be used ++ * in an XML doc. ++ * ++ * @param string the string to escape. ++ * @return the string with appropriate characters escaped. ++ */ ++ public static String escapeForXML(String string) { ++ if (string == null) { ++ return null; ++ } ++ char ch; ++ int i=0; ++ int last=0; ++ char[] input = string.toCharArray(); ++ int len = input.length; ++ StringBuilder out = new StringBuilder((int)(len*1.3)); ++ for (; i < len; i++) { ++ ch = input[i]; ++ if (ch > '>') { ++ } ++ else if (ch == '<') { ++ if (i > last) { ++ out.append(input, last, i - last); ++ } ++ last = i + 1; ++ out.append(LT_ENCODE); ++ } ++ else if (ch == '>') { ++ if (i > last) { ++ out.append(input, last, i - last); ++ } ++ last = i + 1; ++ out.append(GT_ENCODE); ++ } ++ ++ else if (ch == '&') { ++ if (i > last) { ++ out.append(input, last, i - last); ++ } ++ // Do nothing if the string is of the form ë (unicode value) ++ if (!(len > i + 5 ++ && input[i + 1] == '#' ++ && Character.isDigit(input[i + 2]) ++ && Character.isDigit(input[i + 3]) ++ && Character.isDigit(input[i + 4]) ++ && input[i + 5] == ';')) { ++ last = i + 1; ++ out.append(AMP_ENCODE); ++ } ++ } ++ else if (ch == '"') { ++ if (i > last) { ++ out.append(input, last, i - last); ++ } ++ last = i + 1; ++ out.append(QUOTE_ENCODE); ++ } ++ else if (ch == '\'') { ++ if (i > last) { ++ out.append(input, last, i - last); ++ } ++ last = i + 1; ++ out.append(APOS_ENCODE); ++ } ++ } ++ if (last == 0) { ++ return string; ++ } ++ if (i > last) { ++ out.append(input, last, i - last); ++ } ++ return out.toString(); ++ } ++ ++ /** ++ * Used by the hash method. ++ */ ++ private static MessageDigest digest = null; ++ ++ /** ++ * Hashes a String using the SHA-1 algorithm and returns the result as a ++ * String of hexadecimal numbers. This method is synchronized to avoid ++ * excessive MessageDigest object creation. If calling this method becomes ++ * a bottleneck in your code, you may wish to maintain a pool of ++ * MessageDigest objects instead of using this method. ++ *

      ++ * A hash is a one-way function -- that is, given an ++ * input, an output is easily computed. However, given the output, the ++ * input is almost impossible to compute. This is useful for passwords ++ * since we can store the hash and a hacker will then have a very hard time ++ * determining the original password. ++ * ++ * @param data the String to compute the hash of. ++ * @return a hashed version of the passed-in String ++ */ ++ public synchronized static String hash(String data) { ++ if (digest == null) { ++ try { ++ digest = MessageDigest.getInstance("SHA-1"); ++ } ++ catch (NoSuchAlgorithmException nsae) { ++ System.err.println("Failed to load the SHA-1 MessageDigest. " + ++ "Jive will be unable to function normally."); ++ } ++ } ++ // Now, compute hash. ++ try { ++ digest.update(data.getBytes("UTF-8")); ++ } ++ catch (UnsupportedEncodingException e) { ++ System.err.println(e); ++ } ++ return encodeHex(digest.digest()); ++ } ++ ++ /** ++ * Encodes an array of bytes as String representation of hexadecimal. ++ * ++ * @param bytes an array of bytes to convert to a hex string. ++ * @return generated hex string. ++ */ ++ public static String encodeHex(byte[] bytes) { ++ StringBuilder hex = new StringBuilder(bytes.length * 2); ++ ++ for (byte aByte : bytes) { ++ if (((int) aByte & 0xff) < 0x10) { ++ hex.append("0"); ++ } ++ hex.append(Integer.toString((int) aByte & 0xff, 16)); ++ } ++ ++ return hex.toString(); ++ } ++ ++ /** ++ * Encodes a String as a base64 String. ++ * ++ * @param data a String to encode. ++ * @return a base64 encoded String. ++ */ ++ public static String encodeBase64(String data) { ++ byte [] bytes = null; ++ try { ++ bytes = data.getBytes("ISO-8859-1"); ++ } ++ catch (UnsupportedEncodingException uee) { ++ uee.printStackTrace(); ++ } ++ return encodeBase64(bytes); ++ } ++ ++ /** ++ * Encodes a byte array into a base64 String. ++ * ++ * @param data a byte array to encode. ++ * @return a base64 encode String. ++ */ ++ public static String encodeBase64(byte[] data) { ++ return encodeBase64(data, false); ++ } ++ ++ /** ++ * Encodes a byte array into a bse64 String. ++ * ++ * @param data The byte arry to encode. ++ * @param lineBreaks True if the encoding should contain line breaks and false if it should not. ++ * @return A base64 encoded String. ++ */ ++ public static String encodeBase64(byte[] data, boolean lineBreaks) { ++ return encodeBase64(data, 0, data.length, lineBreaks); ++ } ++ ++ /** ++ * Encodes a byte array into a bse64 String. ++ * ++ * @param data The byte arry to encode. ++ * @param offset the offset of the bytearray to begin encoding at. ++ * @param len the length of bytes to encode. ++ * @param lineBreaks True if the encoding should contain line breaks and false if it should not. ++ * @return A base64 encoded String. ++ */ ++ public static String encodeBase64(byte[] data, int offset, int len, boolean lineBreaks) { ++ return Base64.encodeBytes(data, offset, len, (lineBreaks ? Base64.NO_OPTIONS : Base64.DONT_BREAK_LINES)); ++ } ++ ++ /** ++ * Decodes a base64 String. ++ * ++ * @param data a base64 encoded String to decode. ++ * @return the decoded String. ++ */ ++ public static byte[] decodeBase64(String data) { ++ return Base64.decode(data); ++ } ++ ++ /** ++ * Pseudo-random number generator object for use with randomString(). ++ * The Random class is not considered to be cryptographically secure, so ++ * only use these random Strings for low to medium security applications. ++ */ ++ private static Random randGen = new Random(); ++ ++ /** ++ * Array of numbers and letters of mixed case. Numbers appear in the list ++ * twice so that there is a more equal chance that a number will be picked. ++ * We can use the array to get a random number or letter by picking a random ++ * array index. ++ */ ++ private static char[] numbersAndLetters = ("0123456789abcdefghijklmnopqrstuvwxyz" + ++ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray(); ++ ++ /** ++ * Returns a random String of numbers and letters (lower and upper case) ++ * of the specified length. The method uses the Random class that is ++ * built-in to Java which is suitable for low to medium grade security uses. ++ * This means that the output is only pseudo random, i.e., each number is ++ * mathematically generated so is not truly random.

      ++ * ++ * The specified length must be at least one. If not, the method will return ++ * null. ++ * ++ * @param length the desired length of the random String to return. ++ * @return a random String of numbers and letters of the specified length. ++ */ ++ public static String randomString(int length) { ++ if (length < 1) { ++ return null; ++ } ++ // Create a char buffer to put random letters and numbers in. ++ char [] randBuffer = new char[length]; ++ for (int i=0; i { ++ public A a; ++ public B b; ++ ++ public Tuple(A a, B b) { ++ this.a = a; ++ this.b = b; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/WriterListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/WriterListener.java +new file mode 100644 +index 0000000..dcf56d9 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/WriterListener.java +@@ -0,0 +1,41 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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; ++ ++/** ++ * Interface that allows for implementing classes to listen for string writing ++ * events. Listeners are registered with ObservableWriter objects. ++ * ++ * @see ObservableWriter#addWriterListener ++ * @see ObservableWriter#removeWriterListener ++ * ++ * @author Gaston Dombiak ++ */ ++public interface WriterListener { ++ ++ /** ++ * Notification that the Writer has written a new string. ++ * ++ * @param str the written string ++ */ ++ public abstract void write(String str); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractEmptyIterator.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractEmptyIterator.java +new file mode 100644 +index 0000000..c2ec156 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractEmptyIterator.java +@@ -0,0 +1,89 @@ ++// GenericsNote: Converted. ++/* ++ * Copyright 2004 The Apache Software Foundation ++ * ++ * 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.collections; ++ ++import java.util.NoSuchElementException; ++ ++/** ++ * Provides an implementation of an empty iterator. ++ * ++ * @author Matt Hall, John Watkinson, Stephen Colebourne ++ * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:24 $ ++ * @since Commons Collections 3.1 ++ */ ++abstract class AbstractEmptyIterator { ++ ++ /** ++ * Constructor. ++ */ ++ protected AbstractEmptyIterator() { ++ super(); ++ } ++ ++ public boolean hasNext() { ++ return false; ++ } ++ ++ public E next() { ++ throw new NoSuchElementException("Iterator contains no elements"); ++ } ++ ++ public boolean hasPrevious() { ++ return false; ++ } ++ ++ public E previous() { ++ throw new NoSuchElementException("Iterator contains no elements"); ++ } ++ ++ public int nextIndex() { ++ return 0; ++ } ++ ++ public int previousIndex() { ++ return -1; ++ } ++ ++ public void add(E obj) { ++ throw new UnsupportedOperationException("add() not supported for empty Iterator"); ++ } ++ ++ public void set(E obj) { ++ throw new IllegalStateException("Iterator contains no elements"); ++ } ++ ++ public void remove() { ++ throw new IllegalStateException("Iterator contains no elements"); ++ } ++ ++ public E getKey() { ++ throw new IllegalStateException("Iterator contains no elements"); ++ } ++ ++ public E getValue() { ++ throw new IllegalStateException("Iterator contains no elements"); ++ } ++ ++ public E setValue(E value) { ++ throw new IllegalStateException("Iterator contains no elements"); ++ } ++ ++ public void reset() { ++ // do nothing ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractHashedMap.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractHashedMap.java +new file mode 100644 +index 0000000..f6fb34a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractHashedMap.java +@@ -0,0 +1,1338 @@ ++// GenericsNote: Converted -- However, null keys will now be represented in the internal structures, a big change. ++/* ++ * Copyright 2003-2004 The Apache Software Foundation ++ * ++ * 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.collections; ++ ++import java.io.IOException; ++import java.io.ObjectInputStream; ++import java.io.ObjectOutputStream; ++import java.util.*; ++ ++/** ++ * An abstract implementation of a hash-based map which provides numerous points for ++ * subclasses to override. ++ *

      ++ * This class implements all the features necessary for a subclass hash-based map. ++ * Key-value entries are stored in instances of the HashEntry class, ++ * which can be overridden and replaced. The iterators can similarly be replaced, ++ * without the need to replace the KeySet, EntrySet and Values view classes. ++ *

      ++ * Overridable methods are provided to change the default hashing behaviour, and ++ * to change how entries are added to and removed from the map. Hopefully, all you ++ * need for unusual subclasses is here. ++ *

      ++ * NOTE: From Commons Collections 3.1 this class extends AbstractMap. ++ * This is to provide backwards compatibility for ReferenceMap between v3.0 and v3.1. ++ * This extends clause will be removed in v4.0. ++ * ++ * @author java util HashMap ++ * @author Matt Hall, John Watkinson, Stephen Colebourne ++ * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:32 $ ++ * @since Commons Collections 3.0 ++ */ ++public class AbstractHashedMap extends AbstractMap implements IterableMap { ++ ++ protected static final String NO_NEXT_ENTRY = "No next() entry in the iteration"; ++ protected static final String NO_PREVIOUS_ENTRY = "No previous() entry in the iteration"; ++ protected static final String REMOVE_INVALID = "remove() can only be called once after next()"; ++ protected static final String GETKEY_INVALID = "getKey() can only be called after next() and before remove()"; ++ protected static final String GETVALUE_INVALID = "getValue() can only be called after next() and before remove()"; ++ protected static final String SETVALUE_INVALID = "setValue() can only be called after next() and before remove()"; ++ ++ /** ++ * The default capacity to use ++ */ ++ protected static final int DEFAULT_CAPACITY = 16; ++ /** ++ * The default threshold to use ++ */ ++ protected static final int DEFAULT_THRESHOLD = 12; ++ /** ++ * The default load factor to use ++ */ ++ protected static final float DEFAULT_LOAD_FACTOR = 0.75f; ++ /** ++ * The maximum capacity allowed ++ */ ++ protected static final int MAXIMUM_CAPACITY = 1 << 30; ++ /** ++ * An object for masking null ++ */ ++ protected static final Object NULL = new Object(); ++ ++ /** ++ * Load factor, normally 0.75 ++ */ ++ protected transient float loadFactor; ++ /** ++ * The size of the map ++ */ ++ protected transient int size; ++ /** ++ * Map entries ++ */ ++ protected transient HashEntry[] data; ++ /** ++ * Size at which to rehash ++ */ ++ protected transient int threshold; ++ /** ++ * Modification count for iterators ++ */ ++ protected transient int modCount; ++ /** ++ * Entry set ++ */ ++ protected transient EntrySet entrySet; ++ /** ++ * Key set ++ */ ++ protected transient KeySet keySet; ++ /** ++ * Values ++ */ ++ protected transient Values values; ++ ++ /** ++ * Constructor only used in deserialization, do not use otherwise. ++ */ ++ protected AbstractHashedMap() { ++ super(); ++ } ++ ++ /** ++ * Constructor which performs no validation on the passed in parameters. ++ * ++ * @param initialCapacity the initial capacity, must be a power of two ++ * @param loadFactor the load factor, must be > 0.0f and generally < 1.0f ++ * @param threshold the threshold, must be sensible ++ */ ++ protected AbstractHashedMap(int initialCapacity, float loadFactor, int threshold) { ++ super(); ++ this.loadFactor = loadFactor; ++ this.data = new HashEntry[initialCapacity]; ++ this.threshold = threshold; ++ init(); ++ } ++ ++ /** ++ * Constructs a new, empty map with the specified initial capacity and ++ * default load factor. ++ * ++ * @param initialCapacity the initial capacity ++ * @throws IllegalArgumentException if the initial capacity is less than one ++ */ ++ protected AbstractHashedMap(int initialCapacity) { ++ this(initialCapacity, DEFAULT_LOAD_FACTOR); ++ } ++ ++ /** ++ * Constructs a new, empty map with the specified initial capacity and ++ * load factor. ++ * ++ * @param initialCapacity the initial capacity ++ * @param loadFactor the load factor ++ * @throws IllegalArgumentException if the initial capacity is less than one ++ * @throws IllegalArgumentException if the load factor is less than or equal to zero ++ */ ++ protected AbstractHashedMap(int initialCapacity, float loadFactor) { ++ super(); ++ if (initialCapacity < 1) { ++ throw new IllegalArgumentException("Initial capacity must be greater than 0"); ++ } ++ if (loadFactor <= 0.0f || Float.isNaN(loadFactor)) { ++ throw new IllegalArgumentException("Load factor must be greater than 0"); ++ } ++ this.loadFactor = loadFactor; ++ this.threshold = calculateThreshold(initialCapacity, loadFactor); ++ initialCapacity = calculateNewCapacity(initialCapacity); ++ this.data = new HashEntry[initialCapacity]; ++ init(); ++ } ++ ++ /** ++ * Constructor copying elements from another map. ++ * ++ * @param map the map to copy ++ * @throws NullPointerException if the map is null ++ */ ++ protected AbstractHashedMap(Map map) { ++ this(Math.max(2 * map.size(), DEFAULT_CAPACITY), DEFAULT_LOAD_FACTOR); ++ putAll(map); ++ } ++ ++ /** ++ * Initialise subclasses during construction, cloning or deserialization. ++ */ ++ protected void init() { ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Gets the value mapped to the key specified. ++ * ++ * @param key the key ++ * @return the mapped value, null if no match ++ */ ++ public V get(Object key) { ++ int hashCode = hash((key == null) ? NULL : key); ++ HashEntry entry = data[hashIndex(hashCode, data.length)]; // no local for hash index ++ while (entry != null) { ++ if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) { ++ return entry.getValue(); ++ } ++ entry = entry.next; ++ } ++ return null; ++ } ++ ++ /** ++ * Gets the size of the map. ++ * ++ * @return the size ++ */ ++ public int size() { ++ return size; ++ } ++ ++ /** ++ * Checks whether the map is currently empty. ++ * ++ * @return true if the map is currently size zero ++ */ ++ public boolean isEmpty() { ++ return (size == 0); ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Checks whether the map contains the specified key. ++ * ++ * @param key the key to search for ++ * @return true if the map contains the key ++ */ ++ public boolean containsKey(Object key) { ++ int hashCode = hash((key == null) ? NULL : key); ++ HashEntry entry = data[hashIndex(hashCode, data.length)]; // no local for hash index ++ while (entry != null) { ++ if (entry.hashCode == hashCode && isEqualKey(key, entry.getKey())) { ++ return true; ++ } ++ entry = entry.next; ++ } ++ return false; ++ } ++ ++ /** ++ * Checks whether the map contains the specified value. ++ * ++ * @param value the value to search for ++ * @return true if the map contains the value ++ */ ++ public boolean containsValue(Object value) { ++ if (value == null) { ++ for (int i = 0, isize = data.length; i < isize; i++) { ++ HashEntry entry = data[i]; ++ while (entry != null) { ++ if (entry.getValue() == null) { ++ return true; ++ } ++ entry = entry.next; ++ } ++ } ++ } else { ++ for (int i = 0, isize = data.length; i < isize; i++) { ++ HashEntry entry = data[i]; ++ while (entry != null) { ++ if (isEqualValue(value, entry.getValue())) { ++ return true; ++ } ++ entry = entry.next; ++ } ++ } ++ } ++ return false; ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Puts a key-value mapping into this map. ++ * ++ * @param key the key to add ++ * @param value the value to add ++ * @return the value previously mapped to this key, null if none ++ */ ++ public V put(K key, V value) { ++ int hashCode = hash((key == null) ? NULL : key); ++ int index = hashIndex(hashCode, data.length); ++ HashEntry entry = data[index]; ++ while (entry != null) { ++ if (entry.hashCode == hashCode && isEqualKey(key, entry.getKey())) { ++ V oldValue = entry.getValue(); ++ updateEntry(entry, value); ++ return oldValue; ++ } ++ entry = entry.next; ++ } ++ addMapping(index, hashCode, key, value); ++ return null; ++ } ++ ++ /** ++ * Puts all the values from the specified map into this map. ++ *

      ++ * This implementation iterates around the specified map and ++ * uses {@link #put(Object, Object)}. ++ * ++ * @param map the map to add ++ * @throws NullPointerException if the map is null ++ */ ++ public void putAll(Map map) { ++ int mapSize = map.size(); ++ if (mapSize == 0) { ++ return; ++ } ++ int newSize = (int) ((size + mapSize) / loadFactor + 1); ++ ensureCapacity(calculateNewCapacity(newSize)); ++ // Have to cast here because of compiler inference problems. ++ for (Iterator it = map.entrySet().iterator(); it.hasNext();) { ++ Map.Entry entry = (Map.Entry) it.next(); ++ put(entry.getKey(), entry.getValue()); ++ } ++ } ++ ++ /** ++ * Removes the specified mapping from this map. ++ * ++ * @param key the mapping to remove ++ * @return the value mapped to the removed key, null if key not in map ++ */ ++ public V remove(Object key) { ++ int hashCode = hash((key == null) ? NULL : key); ++ int index = hashIndex(hashCode, data.length); ++ HashEntry entry = data[index]; ++ HashEntry previous = null; ++ while (entry != null) { ++ if (entry.hashCode == hashCode && isEqualKey(key, entry.getKey())) { ++ V oldValue = entry.getValue(); ++ removeMapping(entry, index, previous); ++ return oldValue; ++ } ++ previous = entry; ++ entry = entry.next; ++ } ++ return null; ++ } ++ ++ /** ++ * Clears the map, resetting the size to zero and nullifying references ++ * to avoid garbage collection issues. ++ */ ++ public void clear() { ++ modCount++; ++ HashEntry[] data = this.data; ++ for (int i = data.length - 1; i >= 0; i--) { ++ data[i] = null; ++ } ++ size = 0; ++ } ++ ++ /** ++ * Gets the hash code for the key specified. ++ * This implementation uses the additional hashing routine from JDK1.4. ++ * Subclasses can override this to return alternate hash codes. ++ * ++ * @param key the key to get a hash code for ++ * @return the hash code ++ */ ++ protected int hash(Object key) { ++ // same as JDK 1.4 ++ int h = key.hashCode(); ++ h += ~(h << 9); ++ h ^= (h >>> 14); ++ h += (h << 4); ++ h ^= (h >>> 10); ++ return h; ++ } ++ ++ /** ++ * Compares two keys, in internal converted form, to see if they are equal. ++ * This implementation uses the equals method. ++ * Subclasses can override this to match differently. ++ * ++ * @param key1 the first key to compare passed in from outside ++ * @param key2 the second key extracted from the entry via entry.key ++ * @return true if equal ++ */ ++ protected boolean isEqualKey(Object key1, Object key2) { ++ return (key1 == key2 || ((key1 != null) && key1.equals(key2))); ++ } ++ ++ /** ++ * Compares two values, in external form, to see if they are equal. ++ * This implementation uses the equals method and assumes neither value is null. ++ * Subclasses can override this to match differently. ++ * ++ * @param value1 the first value to compare passed in from outside ++ * @param value2 the second value extracted from the entry via getValue() ++ * @return true if equal ++ */ ++ protected boolean isEqualValue(Object value1, Object value2) { ++ return (value1 == value2 || value1.equals(value2)); ++ } ++ ++ /** ++ * Gets the index into the data storage for the hashCode specified. ++ * This implementation uses the least significant bits of the hashCode. ++ * Subclasses can override this to return alternate bucketing. ++ * ++ * @param hashCode the hash code to use ++ * @param dataSize the size of the data to pick a bucket from ++ * @return the bucket index ++ */ ++ protected int hashIndex(int hashCode, int dataSize) { ++ return hashCode & (dataSize - 1); ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Gets the entry mapped to the key specified. ++ *

      ++ * This method exists for subclasses that may need to perform a multi-step ++ * process accessing the entry. The public methods in this class don't use this ++ * method to gain a small performance boost. ++ * ++ * @param key the key ++ * @return the entry, null if no match ++ */ ++ protected HashEntry getEntry(Object key) { ++ int hashCode = hash((key == null) ? NULL : key); ++ HashEntry entry = data[hashIndex(hashCode, data.length)]; // no local for hash index ++ while (entry != null) { ++ if (entry.hashCode == hashCode && isEqualKey(key, entry.getKey())) { ++ return entry; ++ } ++ entry = entry.next; ++ } ++ return null; ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Updates an existing key-value mapping to change the value. ++ *

      ++ * This implementation calls setValue() on the entry. ++ * Subclasses could override to handle changes to the map. ++ * ++ * @param entry the entry to update ++ * @param newValue the new value to store ++ */ ++ protected void updateEntry(HashEntry entry, V newValue) { ++ entry.setValue(newValue); ++ } ++ ++ /** ++ * Reuses an existing key-value mapping, storing completely new data. ++ *

      ++ * This implementation sets all the data fields on the entry. ++ * Subclasses could populate additional entry fields. ++ * ++ * @param entry the entry to update, not null ++ * @param hashIndex the index in the data array ++ * @param hashCode the hash code of the key to add ++ * @param key the key to add ++ * @param value the value to add ++ */ ++ protected void reuseEntry(HashEntry entry, int hashIndex, int hashCode, K key, V value) { ++ entry.next = data[hashIndex]; ++ entry.hashCode = hashCode; ++ entry.key = key; ++ entry.value = value; ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Adds a new key-value mapping into this map. ++ *

      ++ * This implementation calls createEntry(), addEntry() ++ * and checkCapacity(). ++ * It also handles changes to modCount and size. ++ * Subclasses could override to fully control adds to the map. ++ * ++ * @param hashIndex the index into the data array to store at ++ * @param hashCode the hash code of the key to add ++ * @param key the key to add ++ * @param value the value to add ++ */ ++ protected void addMapping(int hashIndex, int hashCode, K key, V value) { ++ modCount++; ++ HashEntry entry = createEntry(data[hashIndex], hashCode, key, value); ++ addEntry(entry, hashIndex); ++ size++; ++ checkCapacity(); ++ } ++ ++ /** ++ * Creates an entry to store the key-value data. ++ *

      ++ * This implementation creates a new HashEntry instance. ++ * Subclasses can override this to return a different storage class, ++ * or implement caching. ++ * ++ * @param next the next entry in sequence ++ * @param hashCode the hash code to use ++ * @param key the key to store ++ * @param value the value to store ++ * @return the newly created entry ++ */ ++ protected HashEntry createEntry(HashEntry next, int hashCode, K key, V value) { ++ return new HashEntry(next, hashCode, key, value); ++ } ++ ++ /** ++ * Adds an entry into this map. ++ *

      ++ * This implementation adds the entry to the data storage table. ++ * Subclasses could override to handle changes to the map. ++ * ++ * @param entry the entry to add ++ * @param hashIndex the index into the data array to store at ++ */ ++ protected void addEntry(HashEntry entry, int hashIndex) { ++ data[hashIndex] = entry; ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Removes a mapping from the map. ++ *

      ++ * This implementation calls removeEntry() and destroyEntry(). ++ * It also handles changes to modCount and size. ++ * Subclasses could override to fully control removals from the map. ++ * ++ * @param entry the entry to remove ++ * @param hashIndex the index into the data structure ++ * @param previous the previous entry in the chain ++ */ ++ protected void removeMapping(HashEntry entry, int hashIndex, HashEntry previous) { ++ modCount++; ++ removeEntry(entry, hashIndex, previous); ++ size--; ++ destroyEntry(entry); ++ } ++ ++ /** ++ * Removes an entry from the chain stored in a particular index. ++ *

      ++ * This implementation removes the entry from the data storage table. ++ * The size is not updated. ++ * Subclasses could override to handle changes to the map. ++ * ++ * @param entry the entry to remove ++ * @param hashIndex the index into the data structure ++ * @param previous the previous entry in the chain ++ */ ++ protected void removeEntry(HashEntry entry, int hashIndex, HashEntry previous) { ++ if (previous == null) { ++ data[hashIndex] = entry.next; ++ } else { ++ previous.next = entry.next; ++ } ++ } ++ ++ /** ++ * Kills an entry ready for the garbage collector. ++ *

      ++ * This implementation prepares the HashEntry for garbage collection. ++ * Subclasses can override this to implement caching (override clear as well). ++ * ++ * @param entry the entry to destroy ++ */ ++ protected void destroyEntry(HashEntry entry) { ++ entry.next = null; ++ entry.key = null; ++ entry.value = null; ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Checks the capacity of the map and enlarges it if necessary. ++ *

      ++ * This implementation uses the threshold to check if the map needs enlarging ++ */ ++ protected void checkCapacity() { ++ if (size >= threshold) { ++ int newCapacity = data.length * 2; ++ if (newCapacity <= MAXIMUM_CAPACITY) { ++ ensureCapacity(newCapacity); ++ } ++ } ++ } ++ ++ /** ++ * Changes the size of the data structure to the capacity proposed. ++ * ++ * @param newCapacity the new capacity of the array (a power of two, less or equal to max) ++ */ ++ protected void ensureCapacity(int newCapacity) { ++ int oldCapacity = data.length; ++ if (newCapacity <= oldCapacity) { ++ return; ++ } ++ if (size == 0) { ++ threshold = calculateThreshold(newCapacity, loadFactor); ++ data = new HashEntry[newCapacity]; ++ } else { ++ HashEntry oldEntries[] = data; ++ HashEntry newEntries[] = new HashEntry[newCapacity]; ++ ++ modCount++; ++ for (int i = oldCapacity - 1; i >= 0; i--) { ++ HashEntry entry = oldEntries[i]; ++ if (entry != null) { ++ oldEntries[i] = null; // gc ++ do { ++ HashEntry next = entry.next; ++ int index = hashIndex(entry.hashCode, newCapacity); ++ entry.next = newEntries[index]; ++ newEntries[index] = entry; ++ entry = next; ++ } while (entry != null); ++ } ++ } ++ threshold = calculateThreshold(newCapacity, loadFactor); ++ data = newEntries; ++ } ++ } ++ ++ /** ++ * Calculates the new capacity of the map. ++ * This implementation normalizes the capacity to a power of two. ++ * ++ * @param proposedCapacity the proposed capacity ++ * @return the normalized new capacity ++ */ ++ protected int calculateNewCapacity(int proposedCapacity) { ++ int newCapacity = 1; ++ if (proposedCapacity > MAXIMUM_CAPACITY) { ++ newCapacity = MAXIMUM_CAPACITY; ++ } else { ++ while (newCapacity < proposedCapacity) { ++ newCapacity <<= 1; // multiply by two ++ } ++ if (newCapacity > MAXIMUM_CAPACITY) { ++ newCapacity = MAXIMUM_CAPACITY; ++ } ++ } ++ return newCapacity; ++ } ++ ++ /** ++ * Calculates the new threshold of the map, where it will be resized. ++ * This implementation uses the load factor. ++ * ++ * @param newCapacity the new capacity ++ * @param factor the load factor ++ * @return the new resize threshold ++ */ ++ protected int calculateThreshold(int newCapacity, float factor) { ++ return (int) (newCapacity * factor); ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Gets the next field from a HashEntry. ++ * Used in subclasses that have no visibility of the field. ++ * ++ * @param entry the entry to query, must not be null ++ * @return the next field of the entry ++ * @throws NullPointerException if the entry is null ++ * @since Commons Collections 3.1 ++ */ ++ protected HashEntry entryNext(HashEntry entry) { ++ return entry.next; ++ } ++ ++ /** ++ * Gets the hashCode field from a HashEntry. ++ * Used in subclasses that have no visibility of the field. ++ * ++ * @param entry the entry to query, must not be null ++ * @return the hashCode field of the entry ++ * @throws NullPointerException if the entry is null ++ * @since Commons Collections 3.1 ++ */ ++ protected int entryHashCode(HashEntry entry) { ++ return entry.hashCode; ++ } ++ ++ /** ++ * Gets the key field from a HashEntry. ++ * Used in subclasses that have no visibility of the field. ++ * ++ * @param entry the entry to query, must not be null ++ * @return the key field of the entry ++ * @throws NullPointerException if the entry is null ++ * @since Commons Collections 3.1 ++ */ ++ protected K entryKey(HashEntry entry) { ++ return entry.key; ++ } ++ ++ /** ++ * Gets the value field from a HashEntry. ++ * Used in subclasses that have no visibility of the field. ++ * ++ * @param entry the entry to query, must not be null ++ * @return the value field of the entry ++ * @throws NullPointerException if the entry is null ++ * @since Commons Collections 3.1 ++ */ ++ protected V entryValue(HashEntry entry) { ++ return entry.value; ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Gets an iterator over the map. ++ * Changes made to the iterator affect this map. ++ *

      ++ * A MapIterator returns the keys in the map. It also provides convenient ++ * methods to get the key and value, and set the value. ++ * It avoids the need to create an entrySet/keySet/values object. ++ * It also avoids creating the Map.Entry object. ++ * ++ * @return the map iterator ++ */ ++ public MapIterator mapIterator() { ++ if (size == 0) { ++ return EmptyMapIterator.INSTANCE; ++ } ++ return new HashMapIterator(this); ++ } ++ ++ /** ++ * MapIterator implementation. ++ */ ++ protected static class HashMapIterator extends HashIterator implements MapIterator { ++ ++ protected HashMapIterator(AbstractHashedMap parent) { ++ super(parent); ++ } ++ ++ public K next() { ++ return super.nextEntry().getKey(); ++ } ++ ++ public K getKey() { ++ HashEntry current = currentEntry(); ++ if (current == null) { ++ throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID); ++ } ++ return current.getKey(); ++ } ++ ++ public V getValue() { ++ HashEntry current = currentEntry(); ++ if (current == null) { ++ throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID); ++ } ++ return current.getValue(); ++ } ++ ++ public V setValue(V value) { ++ HashEntry current = currentEntry(); ++ if (current == null) { ++ throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID); ++ } ++ return current.setValue(value); ++ } ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Gets the entrySet view of the map. ++ * Changes made to the view affect this map. ++ * To simply iterate through the entries, use {@link #mapIterator()}. ++ * ++ * @return the entrySet view ++ */ ++ public Set> entrySet() { ++ if (entrySet == null) { ++ entrySet = new EntrySet(this); ++ } ++ return entrySet; ++ } ++ ++ /** ++ * Creates an entry set iterator. ++ * Subclasses can override this to return iterators with different properties. ++ * ++ * @return the entrySet iterator ++ */ ++ protected Iterator> createEntrySetIterator() { ++ if (size() == 0) { ++ return EmptyIterator.INSTANCE; ++ } ++ return new EntrySetIterator(this); ++ } ++ ++ /** ++ * EntrySet implementation. ++ */ ++ protected static class EntrySet extends AbstractSet> { ++ /** ++ * The parent map ++ */ ++ protected final AbstractHashedMap parent; ++ ++ protected EntrySet(AbstractHashedMap parent) { ++ super(); ++ this.parent = parent; ++ } ++ ++ public int size() { ++ return parent.size(); ++ } ++ ++ public void clear() { ++ parent.clear(); ++ } ++ ++ public boolean contains(Map.Entry entry) { ++ Map.Entry e = entry; ++ Entry match = parent.getEntry(e.getKey()); ++ return (match != null && match.equals(e)); ++ } ++ ++ public boolean remove(Object obj) { ++ if (obj instanceof Map.Entry == false) { ++ return false; ++ } ++ if (contains(obj) == false) { ++ return false; ++ } ++ Map.Entry entry = (Map.Entry) obj; ++ K key = entry.getKey(); ++ parent.remove(key); ++ return true; ++ } ++ ++ public Iterator> iterator() { ++ return parent.createEntrySetIterator(); ++ } ++ } ++ ++ /** ++ * EntrySet iterator. ++ */ ++ protected static class EntrySetIterator extends HashIterator implements Iterator> { ++ ++ protected EntrySetIterator(AbstractHashedMap parent) { ++ super(parent); ++ } ++ ++ public HashEntry next() { ++ return super.nextEntry(); ++ } ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Gets the keySet view of the map. ++ * Changes made to the view affect this map. ++ * To simply iterate through the keys, use {@link #mapIterator()}. ++ * ++ * @return the keySet view ++ */ ++ public Set keySet() { ++ if (keySet == null) { ++ keySet = new KeySet(this); ++ } ++ return keySet; ++ } ++ ++ /** ++ * Creates a key set iterator. ++ * Subclasses can override this to return iterators with different properties. ++ * ++ * @return the keySet iterator ++ */ ++ protected Iterator createKeySetIterator() { ++ if (size() == 0) { ++ return EmptyIterator.INSTANCE; ++ } ++ return new KeySetIterator(this); ++ } ++ ++ /** ++ * KeySet implementation. ++ */ ++ protected static class KeySet extends AbstractSet { ++ /** ++ * The parent map ++ */ ++ protected final AbstractHashedMap parent; ++ ++ protected KeySet(AbstractHashedMap parent) { ++ super(); ++ this.parent = parent; ++ } ++ ++ public int size() { ++ return parent.size(); ++ } ++ ++ public void clear() { ++ parent.clear(); ++ } ++ ++ public boolean contains(Object key) { ++ return parent.containsKey(key); ++ } ++ ++ public boolean remove(Object key) { ++ boolean result = parent.containsKey(key); ++ parent.remove(key); ++ return result; ++ } ++ ++ public Iterator iterator() { ++ return parent.createKeySetIterator(); ++ } ++ } ++ ++ /** ++ * KeySet iterator. ++ */ ++ protected static class KeySetIterator extends HashIterator implements Iterator { ++ ++ protected KeySetIterator(AbstractHashedMap parent) { ++ super(parent); ++ } ++ ++ public K next() { ++ return super.nextEntry().getKey(); ++ } ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Gets the values view of the map. ++ * Changes made to the view affect this map. ++ * To simply iterate through the values, use {@link #mapIterator()}. ++ * ++ * @return the values view ++ */ ++ public Collection values() { ++ if (values == null) { ++ values = new Values(this); ++ } ++ return values; ++ } ++ ++ /** ++ * Creates a values iterator. ++ * Subclasses can override this to return iterators with different properties. ++ * ++ * @return the values iterator ++ */ ++ protected Iterator createValuesIterator() { ++ if (size() == 0) { ++ return EmptyIterator.INSTANCE; ++ } ++ return new ValuesIterator(this); ++ } ++ ++ /** ++ * Values implementation. ++ */ ++ protected static class Values extends AbstractCollection { ++ /** ++ * The parent map ++ */ ++ protected final AbstractHashedMap parent; ++ ++ protected Values(AbstractHashedMap parent) { ++ super(); ++ this.parent = parent; ++ } ++ ++ public int size() { ++ return parent.size(); ++ } ++ ++ public void clear() { ++ parent.clear(); ++ } ++ ++ public boolean contains(Object value) { ++ return parent.containsValue(value); ++ } ++ ++ public Iterator iterator() { ++ return parent.createValuesIterator(); ++ } ++ } ++ ++ /** ++ * Values iterator. ++ */ ++ protected static class ValuesIterator extends HashIterator implements Iterator { ++ ++ protected ValuesIterator(AbstractHashedMap parent) { ++ super(parent); ++ } ++ ++ public V next() { ++ return super.nextEntry().getValue(); ++ } ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * HashEntry used to store the data. ++ *

      ++ * If you subclass AbstractHashedMap but not HashEntry ++ * then you will not be able to access the protected fields. ++ * The entryXxx() methods on AbstractHashedMap exist ++ * to provide the necessary access. ++ */ ++ protected static class HashEntry implements Map.Entry, KeyValue { ++ /** ++ * The next entry in the hash chain ++ */ ++ protected HashEntry next; ++ /** ++ * The hash code of the key ++ */ ++ protected int hashCode; ++ /** ++ * The key ++ */ ++ private K key; ++ /** ++ * The value ++ */ ++ private V value; ++ ++ protected HashEntry(HashEntry next, int hashCode, K key, V value) { ++ super(); ++ this.next = next; ++ this.hashCode = hashCode; ++ this.key = key; ++ this.value = value; ++ } ++ ++ public K getKey() { ++ return key; ++ } ++ ++ public void setKey(K key) { ++ this.key = key; ++ } ++ ++ public V getValue() { ++ return value; ++ } ++ ++ public V setValue(V value) { ++ V old = this.value; ++ this.value = value; ++ return old; ++ } ++ ++ public boolean equals(Object obj) { ++ if (obj == this) { ++ return true; ++ } ++ if (obj instanceof Map.Entry == false) { ++ return false; ++ } ++ Map.Entry other = (Map.Entry) obj; ++ return (getKey() == null ? other.getKey() == null : getKey().equals(other.getKey())) && (getValue() == null ? other.getValue() == null : getValue().equals(other.getValue())); ++ } ++ ++ public int hashCode() { ++ return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode()); ++ } ++ ++ public String toString() { ++ return new StringBuilder().append(getKey()).append('=').append(getValue()).toString(); ++ } ++ } ++ ++ /** ++ * Base Iterator ++ */ ++ protected static abstract class HashIterator { ++ ++ /** ++ * The parent map ++ */ ++ protected final AbstractHashedMap parent; ++ /** ++ * The current index into the array of buckets ++ */ ++ protected int hashIndex; ++ /** ++ * The last returned entry ++ */ ++ protected HashEntry last; ++ /** ++ * The next entry ++ */ ++ protected HashEntry next; ++ /** ++ * The modification count expected ++ */ ++ protected int expectedModCount; ++ ++ protected HashIterator(AbstractHashedMap parent) { ++ super(); ++ this.parent = parent; ++ HashEntry[] data = parent.data; ++ int i = data.length; ++ HashEntry next = null; ++ while (i > 0 && next == null) { ++ next = data[--i]; ++ } ++ this.next = next; ++ this.hashIndex = i; ++ this.expectedModCount = parent.modCount; ++ } ++ ++ public boolean hasNext() { ++ return (next != null); ++ } ++ ++ protected HashEntry nextEntry() { ++ if (parent.modCount != expectedModCount) { ++ throw new ConcurrentModificationException(); ++ } ++ HashEntry newCurrent = next; ++ if (newCurrent == null) { ++ throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY); ++ } ++ HashEntry[] data = parent.data; ++ int i = hashIndex; ++ HashEntry n = newCurrent.next; ++ while (n == null && i > 0) { ++ n = data[--i]; ++ } ++ next = n; ++ hashIndex = i; ++ last = newCurrent; ++ return newCurrent; ++ } ++ ++ protected HashEntry currentEntry() { ++ return last; ++ } ++ ++ public void remove() { ++ if (last == null) { ++ throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID); ++ } ++ if (parent.modCount != expectedModCount) { ++ throw new ConcurrentModificationException(); ++ } ++ parent.remove(last.getKey()); ++ last = null; ++ expectedModCount = parent.modCount; ++ } ++ ++ public String toString() { ++ if (last != null) { ++ return "Iterator[" + last.getKey() + "=" + last.getValue() + "]"; ++ } else { ++ return "Iterator[]"; ++ } ++ } ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Writes the map data to the stream. This method must be overridden if a ++ * subclass must be setup before put() is used. ++ *

      ++ * Serialization is not one of the JDK's nicest topics. Normal serialization will ++ * initialise the superclass before the subclass. Sometimes however, this isn't ++ * what you want, as in this case the put() method on read can be ++ * affected by subclass state. ++ *

      ++ * The solution adopted here is to serialize the state data of this class in ++ * this protected method. This method must be called by the ++ * writeObject() of the first serializable subclass. ++ *

      ++ * Subclasses may override if they have a specific field that must be present ++ * on read before this implementation will work. Generally, the read determines ++ * what must be serialized here, if anything. ++ * ++ * @param out the output stream ++ */ ++ protected void doWriteObject(ObjectOutputStream out) throws IOException { ++ out.writeFloat(loadFactor); ++ out.writeInt(data.length); ++ out.writeInt(size); ++ for (MapIterator it = mapIterator(); it.hasNext();) { ++ out.writeObject(it.next()); ++ out.writeObject(it.getValue()); ++ } ++ } ++ ++ /** ++ * Reads the map data from the stream. This method must be overridden if a ++ * subclass must be setup before put() is used. ++ *

      ++ * Serialization is not one of the JDK's nicest topics. Normal serialization will ++ * initialise the superclass before the subclass. Sometimes however, this isn't ++ * what you want, as in this case the put() method on read can be ++ * affected by subclass state. ++ *

      ++ * The solution adopted here is to deserialize the state data of this class in ++ * this protected method. This method must be called by the ++ * readObject() of the first serializable subclass. ++ *

      ++ * Subclasses may override if the subclass has a specific field that must be present ++ * before put() or calculateThreshold() will work correctly. ++ * ++ * @param in the input stream ++ */ ++ protected void doReadObject(ObjectInputStream in) throws IOException, ClassNotFoundException { ++ loadFactor = in.readFloat(); ++ int capacity = in.readInt(); ++ int size = in.readInt(); ++ init(); ++ data = new HashEntry[capacity]; ++ for (int i = 0; i < size; i++) { ++ K key = (K) in.readObject(); ++ V value = (V) in.readObject(); ++ put(key, value); ++ } ++ threshold = calculateThreshold(data.length, loadFactor); ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Clones the map without cloning the keys or values. ++ *

      ++ * To implement clone(), a subclass must implement the ++ * Cloneable interface and make this method public. ++ * ++ * @return a shallow clone ++ */ ++ protected Object clone() { ++ try { ++ AbstractHashedMap cloned = (AbstractHashedMap) super.clone(); ++ cloned.data = new HashEntry[data.length]; ++ cloned.entrySet = null; ++ cloned.keySet = null; ++ cloned.values = null; ++ cloned.modCount = 0; ++ cloned.size = 0; ++ cloned.init(); ++ cloned.putAll(this); ++ return cloned; ++ ++ } catch (CloneNotSupportedException ex) { ++ return null; // should never happen ++ } ++ } ++ ++ /** ++ * Compares this map with another. ++ * ++ * @param obj the object to compare to ++ * @return true if equal ++ */ ++ public boolean equals(Object obj) { ++ if (obj == this) { ++ return true; ++ } ++ if (obj instanceof Map == false) { ++ return false; ++ } ++ Map map = (Map) obj; ++ if (map.size() != size()) { ++ return false; ++ } ++ MapIterator it = mapIterator(); ++ try { ++ while (it.hasNext()) { ++ Object key = it.next(); ++ Object value = it.getValue(); ++ if (value == null) { ++ if (map.get(key) != null || map.containsKey(key) == false) { ++ return false; ++ } ++ } else { ++ if (value.equals(map.get(key)) == false) { ++ return false; ++ } ++ } ++ } ++ } catch (ClassCastException ignored) { ++ return false; ++ } catch (NullPointerException ignored) { ++ return false; ++ } ++ return true; ++ } ++ ++ /** ++ * Gets the standard Map hashCode. ++ * ++ * @return the hash code defined in the Map interface ++ */ ++ public int hashCode() { ++ int total = 0; ++ Iterator it = createEntrySetIterator(); ++ while (it.hasNext()) { ++ total += it.next().hashCode(); ++ } ++ return total; ++ } ++ ++ /** ++ * Gets the map as a String. ++ * ++ * @return a string version of the map ++ */ ++ public String toString() { ++ if (size() == 0) { ++ return "{}"; ++ } ++ StringBuilder buf = new StringBuilder(32 * size()); ++ buf.append('{'); ++ ++ MapIterator it = mapIterator(); ++ boolean hasNext = it.hasNext(); ++ while (hasNext) { ++ Object key = it.next(); ++ Object value = it.getValue(); ++ buf.append(key == this ? "(this Map)" : key).append('=').append(value == this ? "(this Map)" : value); ++ ++ hasNext = it.hasNext(); ++ if (hasNext) { ++ buf.append(',').append(' '); ++ } ++ } ++ ++ buf.append('}'); ++ return buf.toString(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractKeyValue.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractKeyValue.java +new file mode 100644 +index 0000000..decc342 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractKeyValue.java +@@ -0,0 +1,80 @@ ++// GenericsNote: Converted. ++/* ++ * Copyright 2003-2004 The Apache Software Foundation ++ * ++ * 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.collections; ++ ++ ++/** ++ * Abstract pair class to assist with creating KeyValue and MapEntry implementations. ++ * ++ * @author James Strachan ++ * @author Michael A. Smith ++ * @author Neil O'Toole ++ * @author Matt Hall, John Watkinson, Stephen Colebourne ++ * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:32 $ ++ * @since Commons Collections 3.0 ++ */ ++public abstract class AbstractKeyValue implements KeyValue { ++ ++ /** ++ * The key ++ */ ++ protected K key; ++ /** ++ * The value ++ */ ++ protected V value; ++ ++ /** ++ * Constructs a new pair with the specified key and given value. ++ * ++ * @param key the key for the entry, may be null ++ * @param value the value for the entry, may be null ++ */ ++ protected AbstractKeyValue(K key, V value) { ++ super(); ++ this.key = key; ++ this.value = value; ++ } ++ ++ /** ++ * Gets the key from the pair. ++ * ++ * @return the key ++ */ ++ public K getKey() { ++ return key; ++ } ++ ++ /** ++ * Gets the value from the pair. ++ * ++ * @return the value ++ */ ++ public V getValue() { ++ return value; ++ } ++ ++ /** ++ * Gets a debugging String view of the pair. ++ * ++ * @return a String view of the entry ++ */ ++ public String toString() { ++ return new StringBuilder().append(getKey()).append('=').append(getValue()).toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractMapEntry.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractMapEntry.java +new file mode 100644 +index 0000000..2feb308 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractMapEntry.java +@@ -0,0 +1,89 @@ ++// GenericsNote: Converted. ++/* ++ * Copyright 2003-2004 The Apache Software Foundation ++ * ++ * 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.collections; ++ ++import java.util.Map; ++ ++/** ++ * Abstract Pair class to assist with creating correct Map Entry implementations. ++ * ++ * @author James Strachan ++ * @author Michael A. Smith ++ * @author Neil O'Toole ++ * @author Matt Hall, John Watkinson, Stephen Colebourne ++ * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:32 $ ++ * @since Commons Collections 3.0 ++ */ ++public abstract class AbstractMapEntry extends AbstractKeyValue implements Map.Entry { ++ ++ /** ++ * Constructs a new entry with the given key and given value. ++ * ++ * @param key the key for the entry, may be null ++ * @param value the value for the entry, may be null ++ */ ++ protected AbstractMapEntry(K key, V value) { ++ super(key, value); ++ } ++ ++ // Map.Entry interface ++ //------------------------------------------------------------------------- ++ /** ++ * Sets the value stored in this Map Entry. ++ *

      ++ * This Map Entry is not connected to a Map, so only the local data is changed. ++ * ++ * @param value the new value ++ * @return the previous value ++ */ ++ public V setValue(V value) { ++ V answer = this.value; ++ this.value = value; ++ return answer; ++ } ++ ++ /** ++ * Compares this Map Entry with another Map Entry. ++ *

      ++ * Implemented per API documentation of {@link java.util.Map.Entry#equals(Object)} ++ * ++ * @param obj the object to compare to ++ * @return true if equal key and value ++ */ ++ public boolean equals(Object obj) { ++ if (obj == this) { ++ return true; ++ } ++ if (obj instanceof Map.Entry == false) { ++ return false; ++ } ++ Map.Entry other = (Map.Entry) obj; ++ return (getKey() == null ? other.getKey() == null : getKey().equals(other.getKey())) && (getValue() == null ? other.getValue() == null : getValue().equals(other.getValue())); ++ } ++ ++ /** ++ * Gets a hashCode compatible with the equals method. ++ *

      ++ * Implemented per API documentation of {@link java.util.Map.Entry#hashCode()} ++ * ++ * @return a suitable hash code ++ */ ++ public int hashCode() { ++ return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode()); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractReferenceMap.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractReferenceMap.java +new file mode 100644 +index 0000000..b57f17d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractReferenceMap.java +@@ -0,0 +1,1025 @@ ++// Converted, with some major refactors required. Not as memory-efficient as before, could use additional refactoring. ++// Perhaps use four different types of HashEntry classes for max efficiency: ++// normal HashEntry for HARD,HARD ++// HardRefEntry for HARD,(SOFT|WEAK) ++// RefHardEntry for (SOFT|WEAK),HARD ++// RefRefEntry for (SOFT|WEAK),(SOFT|WEAK) ++/* ++ * Copyright 2002-2004 The Apache Software Foundation ++ * ++ * 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.collections; ++ ++import java.io.IOException; ++import java.io.ObjectInputStream; ++import java.io.ObjectOutputStream; ++import java.lang.ref.Reference; ++import java.lang.ref.ReferenceQueue; ++import java.lang.ref.SoftReference; ++import java.lang.ref.WeakReference; ++import java.util.*; ++ ++/** ++ * An abstract implementation of a hash-based map that allows the entries to ++ * be removed by the garbage collector. ++ *

      ++ * This class implements all the features necessary for a subclass reference ++ * hash-based map. Key-value entries are stored in instances of the ++ * ReferenceEntry class which can be overridden and replaced. ++ * The iterators can similarly be replaced, without the need to replace the KeySet, ++ * EntrySet and Values view classes. ++ *

      ++ * Overridable methods are provided to change the default hashing behaviour, and ++ * to change how entries are added to and removed from the map. Hopefully, all you ++ * need for unusual subclasses is here. ++ *

      ++ * When you construct an AbstractReferenceMap, you can specify what ++ * kind of references are used to store the map's keys and values. ++ * If non-hard references are used, then the garbage collector can remove ++ * mappings if a key or value becomes unreachable, or if the JVM's memory is ++ * running low. For information on how the different reference types behave, ++ * see {@link Reference}. ++ *

      ++ * Different types of references can be specified for keys and values. ++ * The keys can be configured to be weak but the values hard, ++ * in which case this class will behave like a ++ * ++ * WeakHashMap. However, you can also specify hard keys and ++ * weak values, or any other combination. The default constructor uses ++ * hard keys and soft values, providing a memory-sensitive cache. ++ *

      ++ * This {@link Map} implementation does not allow null elements. ++ * Attempting to add a null key or value to the map will raise a ++ * NullPointerException. ++ *

      ++ * All the available iterators can be reset back to the start by casting to ++ * ResettableIterator and calling reset(). ++ *

      ++ * This implementation is not synchronized. ++ * You can use {@link java.util.Collections#synchronizedMap} to ++ * provide synchronized access to a ReferenceMap. ++ * ++ * @author Paul Jack ++ * @author Matt Hall, John Watkinson, Stephen Colebourne ++ * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:32 $ ++ * @see java.lang.ref.Reference ++ * @since Commons Collections 3.1 (extracted from ReferenceMap in 3.0) ++ */ ++public abstract class AbstractReferenceMap extends AbstractHashedMap { ++ ++ /** ++ * Constant indicating that hard references should be used ++ */ ++ public static final int HARD = 0; ++ ++ /** ++ * Constant indicating that soft references should be used ++ */ ++ public static final int SOFT = 1; ++ ++ /** ++ * Constant indicating that weak references should be used ++ */ ++ public static final int WEAK = 2; ++ ++ /** ++ * The reference type for keys. Must be HARD, SOFT, WEAK. ++ * ++ * @serial ++ */ ++ protected int keyType; ++ ++ /** ++ * The reference type for values. Must be HARD, SOFT, WEAK. ++ * ++ * @serial ++ */ ++ protected int valueType; ++ ++ /** ++ * Should the value be automatically purged when the associated key has been collected? ++ */ ++ protected boolean purgeValues; ++ ++ /** ++ * ReferenceQueue used to eliminate stale mappings. ++ * See purge. ++ */ ++ private transient ReferenceQueue queue; ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Constructor used during deserialization. ++ */ ++ protected AbstractReferenceMap() { ++ super(); ++ } ++ ++ /** ++ * Constructs a new empty map with the specified reference types, ++ * load factor and initial capacity. ++ * ++ * @param keyType the type of reference to use for keys; ++ * must be {@link #SOFT} or {@link #WEAK} ++ * @param valueType the type of reference to use for values; ++ * must be {@link #SOFT} or {@link #WEAK} ++ * @param capacity the initial capacity for the map ++ * @param loadFactor the load factor for the map ++ * @param purgeValues should the value be automatically purged when the ++ * key is garbage collected ++ */ ++ protected AbstractReferenceMap(int keyType, int valueType, int capacity, float loadFactor, boolean purgeValues) { ++ super(capacity, loadFactor); ++ verify("keyType", keyType); ++ verify("valueType", valueType); ++ this.keyType = keyType; ++ this.valueType = valueType; ++ this.purgeValues = purgeValues; ++ } ++ ++ /** ++ * Initialise this subclass during construction, cloning or deserialization. ++ */ ++ protected void init() { ++ queue = new ReferenceQueue(); ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Checks the type int is a valid value. ++ * ++ * @param name the name for error messages ++ * @param type the type value to check ++ * @throws IllegalArgumentException if the value if invalid ++ */ ++ private static void verify(String name, int type) { ++ if ((type < HARD) || (type > WEAK)) { ++ throw new IllegalArgumentException(name + " must be HARD, SOFT, WEAK."); ++ } ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Gets the size of the map. ++ * ++ * @return the size ++ */ ++ public int size() { ++ purgeBeforeRead(); ++ return super.size(); ++ } ++ ++ /** ++ * Checks whether the map is currently empty. ++ * ++ * @return true if the map is currently size zero ++ */ ++ public boolean isEmpty() { ++ purgeBeforeRead(); ++ return super.isEmpty(); ++ } ++ ++ /** ++ * Checks whether the map contains the specified key. ++ * ++ * @param key the key to search for ++ * @return true if the map contains the key ++ */ ++ public boolean containsKey(Object key) { ++ purgeBeforeRead(); ++ Entry entry = getEntry(key); ++ if (entry == null) { ++ return false; ++ } ++ return (entry.getValue() != null); ++ } ++ ++ /** ++ * Checks whether the map contains the specified value. ++ * ++ * @param value the value to search for ++ * @return true if the map contains the value ++ */ ++ public boolean containsValue(Object value) { ++ purgeBeforeRead(); ++ if (value == null) { ++ return false; ++ } ++ return super.containsValue(value); ++ } ++ ++ /** ++ * Gets the value mapped to the key specified. ++ * ++ * @param key the key ++ * @return the mapped value, null if no match ++ */ ++ public V get(Object key) { ++ purgeBeforeRead(); ++ Entry entry = getEntry(key); ++ if (entry == null) { ++ return null; ++ } ++ return entry.getValue(); ++ } ++ ++ ++ /** ++ * Puts a key-value mapping into this map. ++ * Neither the key nor the value may be null. ++ * ++ * @param key the key to add, must not be null ++ * @param value the value to add, must not be null ++ * @return the value previously mapped to this key, null if none ++ * @throws NullPointerException if either the key or value is null ++ */ ++ public V put(K key, V value) { ++ if (key == null) { ++ throw new NullPointerException("null keys not allowed"); ++ } ++ if (value == null) { ++ throw new NullPointerException("null values not allowed"); ++ } ++ ++ purgeBeforeWrite(); ++ return super.put(key, value); ++ } ++ ++ /** ++ * Removes the specified mapping from this map. ++ * ++ * @param key the mapping to remove ++ * @return the value mapped to the removed key, null if key not in map ++ */ ++ public V remove(Object key) { ++ if (key == null) { ++ return null; ++ } ++ purgeBeforeWrite(); ++ return super.remove(key); ++ } ++ ++ /** ++ * Clears this map. ++ */ ++ public void clear() { ++ super.clear(); ++ while (queue.poll() != null) { ++ } // drain the queue ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Gets a MapIterator over the reference map. ++ * The iterator only returns valid key/value pairs. ++ * ++ * @return a map iterator ++ */ ++ public MapIterator mapIterator() { ++ return new ReferenceMapIterator(this); ++ } ++ ++ /** ++ * Returns a set view of this map's entries. ++ * An iterator returned entry is valid until next() is called again. ++ * The setValue() method on the toArray entries has no effect. ++ * ++ * @return a set view of this map's entries ++ */ ++ public Set> entrySet() { ++ if (entrySet == null) { ++ entrySet = new ReferenceEntrySet(this); ++ } ++ return entrySet; ++ } ++ ++ /** ++ * Returns a set view of this map's keys. ++ * ++ * @return a set view of this map's keys ++ */ ++ public Set keySet() { ++ if (keySet == null) { ++ keySet = new ReferenceKeySet(this); ++ } ++ return keySet; ++ } ++ ++ /** ++ * Returns a collection view of this map's values. ++ * ++ * @return a set view of this map's values ++ */ ++ public Collection values() { ++ if (values == null) { ++ values = new ReferenceValues(this); ++ } ++ return values; ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Purges stale mappings from this map before read operations. ++ *

      ++ * This implementation calls {@link #purge()} to maintain a consistent state. ++ */ ++ protected void purgeBeforeRead() { ++ purge(); ++ } ++ ++ /** ++ * Purges stale mappings from this map before write operations. ++ *

      ++ * This implementation calls {@link #purge()} to maintain a consistent state. ++ */ ++ protected void purgeBeforeWrite() { ++ purge(); ++ } ++ ++ /** ++ * Purges stale mappings from this map. ++ *

      ++ * Note that this method is not synchronized! Special ++ * care must be taken if, for instance, you want stale ++ * mappings to be removed on a periodic basis by some ++ * background thread. ++ */ ++ protected void purge() { ++ Reference ref = queue.poll(); ++ while (ref != null) { ++ purge(ref); ++ ref = queue.poll(); ++ } ++ } ++ ++ /** ++ * Purges the specified reference. ++ * ++ * @param ref the reference to purge ++ */ ++ protected void purge(Reference ref) { ++ // The hashCode of the reference is the hashCode of the ++ // mapping key, even if the reference refers to the ++ // mapping value... ++ int hash = ref.hashCode(); ++ int index = hashIndex(hash, data.length); ++ HashEntry previous = null; ++ HashEntry entry = data[index]; ++ while (entry != null) { ++ if (((ReferenceEntry) entry).purge(ref)) { ++ if (previous == null) { ++ data[index] = entry.next; ++ } else { ++ previous.next = entry.next; ++ } ++ this.size--; ++ return; ++ } ++ previous = entry; ++ entry = entry.next; ++ } ++ ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Gets the entry mapped to the key specified. ++ * ++ * @param key the key ++ * @return the entry, null if no match ++ */ ++ protected HashEntry getEntry(Object key) { ++ if (key == null) { ++ return null; ++ } else { ++ return super.getEntry(key); ++ } ++ } ++ ++ /** ++ * Gets the hash code for a MapEntry. ++ * Subclasses can override this, for example to use the identityHashCode. ++ * ++ * @param key the key to get a hash code for, may be null ++ * @param value the value to get a hash code for, may be null ++ * @return the hash code, as per the MapEntry specification ++ */ ++ protected int hashEntry(Object key, Object value) { ++ return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); ++ } ++ ++ /** ++ * Compares two keys, in internal converted form, to see if they are equal. ++ *

      ++ * This implementation converts the key from the entry to a real reference ++ * before comparison. ++ * ++ * @param key1 the first key to compare passed in from outside ++ * @param key2 the second key extracted from the entry via entry.key ++ * @return true if equal ++ */ ++ protected boolean isEqualKey(Object key1, Object key2) { ++ //if ((key1 == null) && (key2 != null) || (key1 != null) || (key2 == null)) { ++ // return false; ++ //} ++ // GenericsNote: Conversion from reference handled by getKey() which replaced all .key references ++ //key2 = (keyType > HARD ? ((Reference) key2).get() : key2); ++ return (key1 == key2 || key1.equals(key2)); ++ } ++ ++ /** ++ * Creates a ReferenceEntry instead of a HashEntry. ++ * ++ * @param next the next entry in sequence ++ * @param hashCode the hash code to use ++ * @param key the key to store ++ * @param value the value to store ++ * @return the newly created entry ++ */ ++ public HashEntry createEntry(HashEntry next, int hashCode, K key, V value) { ++ return new ReferenceEntry(this, (ReferenceEntry) next, hashCode, key, value); ++ } ++ ++ /** ++ * Creates an entry set iterator. ++ * ++ * @return the entrySet iterator ++ */ ++ protected Iterator> createEntrySetIterator() { ++ return new ReferenceEntrySetIterator(this); ++ } ++ ++ /** ++ * Creates an key set iterator. ++ * ++ * @return the keySet iterator ++ */ ++ protected Iterator createKeySetIterator() { ++ return new ReferenceKeySetIterator(this); ++ } ++ ++ /** ++ * Creates an values iterator. ++ * ++ * @return the values iterator ++ */ ++ protected Iterator createValuesIterator() { ++ return new ReferenceValuesIterator(this); ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * EntrySet implementation. ++ */ ++ static class ReferenceEntrySet extends EntrySet { ++ ++ protected ReferenceEntrySet(AbstractHashedMap parent) { ++ super(parent); ++ } ++ ++ public Object[] toArray() { ++ return toArray(new Object[0]); ++ } ++ ++ public T[] toArray(T[] arr) { ++ // special implementation to handle disappearing entries ++ ArrayList> list = new ArrayList>(); ++ Iterator> iterator = iterator(); ++ while (iterator.hasNext()) { ++ Map.Entry e = iterator.next(); ++ list.add(new DefaultMapEntry(e.getKey(), e.getValue())); ++ } ++ return list.toArray(arr); ++ } ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * KeySet implementation. ++ */ ++ static class ReferenceKeySet extends KeySet { ++ ++ protected ReferenceKeySet(AbstractHashedMap parent) { ++ super(parent); ++ } ++ ++ public Object[] toArray() { ++ return toArray(new Object[0]); ++ } ++ ++ public T[] toArray(T[] arr) { ++ // special implementation to handle disappearing keys ++ List list = new ArrayList(parent.size()); ++ for (Iterator it = iterator(); it.hasNext();) { ++ list.add(it.next()); ++ } ++ return list.toArray(arr); ++ } ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Values implementation. ++ */ ++ static class ReferenceValues extends Values { ++ ++ protected ReferenceValues(AbstractHashedMap parent) { ++ super(parent); ++ } ++ ++ public Object[] toArray() { ++ return toArray(new Object[0]); ++ } ++ ++ public T[] toArray(T[] arr) { ++ // special implementation to handle disappearing values ++ List list = new ArrayList(parent.size()); ++ for (Iterator it = iterator(); it.hasNext();) { ++ list.add(it.next()); ++ } ++ return list.toArray(arr); ++ } ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * A MapEntry implementation for the map. ++ *

      ++ * If getKey() or getValue() returns null, it means ++ * the mapping is stale and should be removed. ++ * ++ * @since Commons Collections 3.1 ++ */ ++ protected static class ReferenceEntry extends HashEntry { ++ /** ++ * The parent map ++ */ ++ protected final AbstractReferenceMap parent; ++ ++ protected Reference refKey; ++ protected Reference refValue; ++ ++ /** ++ * Creates a new entry object for the ReferenceMap. ++ * ++ * @param parent the parent map ++ * @param next the next entry in the hash bucket ++ * @param hashCode the hash code of the key ++ * @param key the key ++ * @param value the value ++ */ ++ public ReferenceEntry(AbstractReferenceMap parent, ReferenceEntry next, int hashCode, K key, V value) { ++ super(next, hashCode, null, null); ++ this.parent = parent; ++ if (parent.keyType != HARD) { ++ refKey = toReference(parent.keyType, key, hashCode); ++ } else { ++ this.setKey(key); ++ } ++ if (parent.valueType != HARD) { ++ refValue = toReference(parent.valueType, value, hashCode); // the key hashCode is passed in deliberately ++ } else { ++ this.setValue(value); ++ } ++ } ++ ++ /** ++ * Gets the key from the entry. ++ * This method dereferences weak and soft keys and thus may return null. ++ * ++ * @return the key, which may be null if it was garbage collected ++ */ ++ public K getKey() { ++ return (parent.keyType > HARD) ? refKey.get() : super.getKey(); ++ } ++ ++ /** ++ * Gets the value from the entry. ++ * This method dereferences weak and soft value and thus may return null. ++ * ++ * @return the value, which may be null if it was garbage collected ++ */ ++ public V getValue() { ++ return (parent.valueType > HARD) ? refValue.get() : super.getValue(); ++ } ++ ++ /** ++ * Sets the value of the entry. ++ * ++ * @param obj the object to store ++ * @return the previous value ++ */ ++ public V setValue(V obj) { ++ V old = getValue(); ++ if (parent.valueType > HARD) { ++ refValue.clear(); ++ refValue = toReference(parent.valueType, obj, hashCode); ++ } else { ++ super.setValue(obj); ++ } ++ return old; ++ } ++ ++ /** ++ * Compares this map entry to another. ++ *

      ++ * This implementation uses isEqualKey and ++ * isEqualValue on the main map for comparison. ++ * ++ * @param obj the other map entry to compare to ++ * @return true if equal, false if not ++ */ ++ public boolean equals(Object obj) { ++ if (obj == this) { ++ return true; ++ } ++ if (obj instanceof Map.Entry == false) { ++ return false; ++ } ++ ++ Map.Entry entry = (Map.Entry) obj; ++ Object entryKey = entry.getKey(); // convert to hard reference ++ Object entryValue = entry.getValue(); // convert to hard reference ++ if ((entryKey == null) || (entryValue == null)) { ++ return false; ++ } ++ // compare using map methods, aiding identity subclass ++ // note that key is direct access and value is via method ++ return parent.isEqualKey(entryKey, getKey()) && parent.isEqualValue(entryValue, getValue()); ++ } ++ ++ /** ++ * Gets the hashcode of the entry using temporary hard references. ++ *

      ++ * This implementation uses hashEntry on the main map. ++ * ++ * @return the hashcode of the entry ++ */ ++ public int hashCode() { ++ return parent.hashEntry(getKey(), getValue()); ++ } ++ ++ /** ++ * Constructs a reference of the given type to the given referent. ++ * The reference is registered with the queue for later purging. ++ * ++ * @param type HARD, SOFT or WEAK ++ * @param referent the object to refer to ++ * @param hash the hash code of the key of the mapping; ++ * this number might be different from referent.hashCode() if ++ * the referent represents a value and not a key ++ */ ++ protected Reference toReference(int type, T referent, int hash) { ++ switch (type) { ++ case SOFT: ++ return new SoftRef(hash, referent, parent.queue); ++ case WEAK: ++ return new WeakRef(hash, referent, parent.queue); ++ default: ++ throw new Error("Attempt to create hard reference in ReferenceMap!"); ++ } ++ } ++ ++ /** ++ * Purges the specified reference ++ * ++ * @param ref the reference to purge ++ * @return true or false ++ */ ++ boolean purge(Reference ref) { ++ boolean r = (parent.keyType > HARD) && (refKey == ref); ++ r = r || ((parent.valueType > HARD) && (refValue == ref)); ++ if (r) { ++ if (parent.keyType > HARD) { ++ refKey.clear(); ++ } ++ if (parent.valueType > HARD) { ++ refValue.clear(); ++ } else if (parent.purgeValues) { ++ setValue(null); ++ } ++ } ++ return r; ++ } ++ ++ /** ++ * Gets the next entry in the bucket. ++ * ++ * @return the next entry in the bucket ++ */ ++ protected ReferenceEntry next() { ++ return (ReferenceEntry) next; ++ } ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * The EntrySet iterator. ++ */ ++ static class ReferenceIteratorBase { ++ /** ++ * The parent map ++ */ ++ final AbstractReferenceMap parent; ++ ++ // These fields keep track of where we are in the table. ++ int index; ++ ReferenceEntry entry; ++ ReferenceEntry previous; ++ ++ // These Object fields provide hard references to the ++ // current and next entry; this assures that if hasNext() ++ // returns true, next() will actually return a valid element. ++ K nextKey; ++ V nextValue; ++ K currentKey; ++ V currentValue; ++ ++ int expectedModCount; ++ ++ public ReferenceIteratorBase(AbstractReferenceMap parent) { ++ super(); ++ this.parent = parent; ++ index = (parent.size() != 0 ? parent.data.length : 0); ++ // have to do this here! size() invocation above ++ // may have altered the modCount. ++ expectedModCount = parent.modCount; ++ } ++ ++ public boolean hasNext() { ++ checkMod(); ++ while (nextNull()) { ++ ReferenceEntry e = entry; ++ int i = index; ++ while ((e == null) && (i > 0)) { ++ i--; ++ e = (ReferenceEntry) parent.data[i]; ++ } ++ entry = e; ++ index = i; ++ if (e == null) { ++ currentKey = null; ++ currentValue = null; ++ return false; ++ } ++ nextKey = e.getKey(); ++ nextValue = e.getValue(); ++ if (nextNull()) { ++ entry = entry.next(); ++ } ++ } ++ return true; ++ } ++ ++ private void checkMod() { ++ if (parent.modCount != expectedModCount) { ++ throw new ConcurrentModificationException(); ++ } ++ } ++ ++ private boolean nextNull() { ++ return (nextKey == null) || (nextValue == null); ++ } ++ ++ protected ReferenceEntry nextEntry() { ++ checkMod(); ++ if (nextNull() && !hasNext()) { ++ throw new NoSuchElementException(); ++ } ++ previous = entry; ++ entry = entry.next(); ++ currentKey = nextKey; ++ currentValue = nextValue; ++ nextKey = null; ++ nextValue = null; ++ return previous; ++ } ++ ++ protected ReferenceEntry currentEntry() { ++ checkMod(); ++ return previous; ++ } ++ ++ public ReferenceEntry superNext() { ++ return nextEntry(); ++ } ++ ++ public void remove() { ++ checkMod(); ++ if (previous == null) { ++ throw new IllegalStateException(); ++ } ++ parent.remove(currentKey); ++ previous = null; ++ currentKey = null; ++ currentValue = null; ++ expectedModCount = parent.modCount; ++ } ++ } ++ ++ /** ++ * The EntrySet iterator. ++ */ ++ static class ReferenceEntrySetIterator extends ReferenceIteratorBase implements Iterator> { ++ ++ public ReferenceEntrySetIterator(AbstractReferenceMap abstractReferenceMap) { ++ super(abstractReferenceMap); ++ } ++ ++ public ReferenceEntry next() { ++ return superNext(); ++ } ++ ++ } ++ ++ /** ++ * The keySet iterator. ++ */ ++ static class ReferenceKeySetIterator extends ReferenceIteratorBase implements Iterator { ++ ++ ReferenceKeySetIterator(AbstractReferenceMap parent) { ++ super(parent); ++ } ++ ++ public K next() { ++ return nextEntry().getKey(); ++ } ++ } ++ ++ /** ++ * The values iterator. ++ */ ++ static class ReferenceValuesIterator extends ReferenceIteratorBase implements Iterator { ++ ++ ReferenceValuesIterator(AbstractReferenceMap parent) { ++ super(parent); ++ } ++ ++ public V next() { ++ return nextEntry().getValue(); ++ } ++ } ++ ++ /** ++ * The MapIterator implementation. ++ */ ++ static class ReferenceMapIterator extends ReferenceIteratorBase implements MapIterator { ++ ++ protected ReferenceMapIterator(AbstractReferenceMap parent) { ++ super(parent); ++ } ++ ++ public K next() { ++ return nextEntry().getKey(); ++ } ++ ++ public K getKey() { ++ HashEntry current = currentEntry(); ++ if (current == null) { ++ throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID); ++ } ++ return current.getKey(); ++ } ++ ++ public V getValue() { ++ HashEntry current = currentEntry(); ++ if (current == null) { ++ throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID); ++ } ++ return current.getValue(); ++ } ++ ++ public V setValue(V value) { ++ HashEntry current = currentEntry(); ++ if (current == null) { ++ throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID); ++ } ++ return current.setValue(value); ++ } ++ } ++ ++ //----------------------------------------------------------------------- ++ // These two classes store the hashCode of the key of ++ // of the mapping, so that after they're dequeued a quick ++ // lookup of the bucket in the table can occur. ++ ++ /** ++ * A soft reference holder. ++ */ ++ static class SoftRef extends SoftReference { ++ /** ++ * the hashCode of the key (even if the reference points to a value) ++ */ ++ private int hash; ++ ++ public SoftRef(int hash, T r, ReferenceQueue q) { ++ super(r, q); ++ this.hash = hash; ++ } ++ ++ public int hashCode() { ++ return hash; ++ } ++ } ++ ++ /** ++ * A weak reference holder. ++ */ ++ static class WeakRef extends WeakReference { ++ /** ++ * the hashCode of the key (even if the reference points to a value) ++ */ ++ private int hash; ++ ++ public WeakRef(int hash, T r, ReferenceQueue q) { ++ super(r, q); ++ this.hash = hash; ++ } ++ ++ public int hashCode() { ++ return hash; ++ } ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Replaces the superclass method to store the state of this class. ++ *

      ++ * Serialization is not one of the JDK's nicest topics. Normal serialization will ++ * initialise the superclass before the subclass. Sometimes however, this isn't ++ * what you want, as in this case the put() method on read can be ++ * affected by subclass state. ++ *

      ++ * The solution adopted here is to serialize the state data of this class in ++ * this protected method. This method must be called by the ++ * writeObject() of the first serializable subclass. ++ *

      ++ * Subclasses may override if they have a specific field that must be present ++ * on read before this implementation will work. Generally, the read determines ++ * what must be serialized here, if anything. ++ * ++ * @param out the output stream ++ */ ++ protected void doWriteObject(ObjectOutputStream out) throws IOException { ++ out.writeInt(keyType); ++ out.writeInt(valueType); ++ out.writeBoolean(purgeValues); ++ out.writeFloat(loadFactor); ++ out.writeInt(data.length); ++ for (MapIterator it = mapIterator(); it.hasNext();) { ++ out.writeObject(it.next()); ++ out.writeObject(it.getValue()); ++ } ++ out.writeObject(null); // null terminate map ++ // do not call super.doWriteObject() as code there doesn't work for reference map ++ } ++ ++ /** ++ * Replaces the superclassm method to read the state of this class. ++ *

      ++ * Serialization is not one of the JDK's nicest topics. Normal serialization will ++ * initialise the superclass before the subclass. Sometimes however, this isn't ++ * what you want, as in this case the put() method on read can be ++ * affected by subclass state. ++ *

      ++ * The solution adopted here is to deserialize the state data of this class in ++ * this protected method. This method must be called by the ++ * readObject() of the first serializable subclass. ++ *

      ++ * Subclasses may override if the subclass has a specific field that must be present ++ * before put() or calculateThreshold() will work correctly. ++ * ++ * @param in the input stream ++ */ ++ protected void doReadObject(ObjectInputStream in) throws IOException, ClassNotFoundException { ++ this.keyType = in.readInt(); ++ this.valueType = in.readInt(); ++ this.purgeValues = in.readBoolean(); ++ this.loadFactor = in.readFloat(); ++ int capacity = in.readInt(); ++ init(); ++ data = new HashEntry[capacity]; ++ while (true) { ++ K key = (K) in.readObject(); ++ if (key == null) { ++ break; ++ } ++ V value = (V) in.readObject(); ++ put(key, value); ++ } ++ threshold = calculateThreshold(data.length, loadFactor); ++ // do not call super.doReadObject() as code there doesn't work for reference map ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/DefaultMapEntry.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/DefaultMapEntry.java +new file mode 100644 +index 0000000..ef752d0 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/DefaultMapEntry.java +@@ -0,0 +1,65 @@ ++// GenericsNote: Converted. ++/* ++ * Copyright 2001-2004 The Apache Software Foundation ++ * ++ * 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.collections; ++ ++ ++import java.util.Map; ++ ++/** ++ * A restricted implementation of {@link java.util.Map.Entry} that prevents ++ * the MapEntry contract from being broken. ++ * ++ * @author James Strachan ++ * @author Michael A. Smith ++ * @author Neil O'Toole ++ * @author Matt Hall, John Watkinson, Stephen Colebourne ++ * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:32 $ ++ * @since Commons Collections 3.0 ++ */ ++public final class DefaultMapEntry extends AbstractMapEntry { ++ ++ /** ++ * Constructs a new entry with the specified key and given value. ++ * ++ * @param key the key for the entry, may be null ++ * @param value the value for the entry, may be null ++ */ ++ public DefaultMapEntry(final K key, final V value) { ++ super(key, value); ++ } ++ ++ /** ++ * Constructs a new entry from the specified KeyValue. ++ * ++ * @param pair the pair to copy, must not be null ++ * @throws NullPointerException if the entry is null ++ */ ++ public DefaultMapEntry(final KeyValue pair) { ++ super(pair.getKey(), pair.getValue()); ++ } ++ ++ /** ++ * Constructs a new entry from the specified MapEntry. ++ * ++ * @param entry the entry to copy, must not be null ++ * @throws NullPointerException if the entry is null ++ */ ++ public DefaultMapEntry(final Map.Entry entry) { ++ super(entry.getKey(), entry.getValue()); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/EmptyIterator.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/EmptyIterator.java +new file mode 100644 +index 0000000..6a8707f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/EmptyIterator.java +@@ -0,0 +1,58 @@ ++// GenericsNote: Converted. ++/* ++ * Copyright 2004 The Apache Software Foundation ++ * ++ * 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.collections; ++ ++import java.util.Iterator; ++ ++/** ++ * Provides an implementation of an empty iterator. ++ *

      ++ * This class provides an implementation of an empty iterator. ++ * This class provides for binary compatability between Commons Collections ++ * 2.1.1 and 3.1 due to issues with IteratorUtils. ++ * ++ * @author Matt Hall, John Watkinson, Stephen Colebourne ++ * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:24 $ ++ * @since Commons Collections 2.1.1 and 3.1 ++ */ ++public class EmptyIterator extends AbstractEmptyIterator implements ResettableIterator { ++ ++ /** ++ * Singleton instance of the iterator. ++ * ++ * @since Commons Collections 3.1 ++ */ ++ public static final ResettableIterator RESETTABLE_INSTANCE = new EmptyIterator(); ++ /** ++ * Singleton instance of the iterator. ++ * ++ * @since Commons Collections 2.1.1 and 3.1 ++ */ ++ public static final Iterator INSTANCE = RESETTABLE_INSTANCE; ++ ++ public static Iterator getInstance() { ++ return INSTANCE; ++ } ++ ++ /** ++ * Constructor. ++ */ ++ protected EmptyIterator() { ++ super(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/EmptyMapIterator.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/EmptyMapIterator.java +new file mode 100644 +index 0000000..013f5ed +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/EmptyMapIterator.java +@@ -0,0 +1,42 @@ ++// GenericsNote: Converted. ++/* ++ * Copyright 2004 The Apache Software Foundation ++ * ++ * 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.collections; ++ ++/** ++ * Provides an implementation of an empty map iterator. ++ * ++ * @author Matt Hall, John Watkinson, Stephen Colebourne ++ * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:24 $ ++ * @since Commons Collections 3.1 ++ */ ++public class EmptyMapIterator extends AbstractEmptyIterator implements MapIterator, ResettableIterator { ++ ++ /** ++ * Singleton instance of the iterator. ++ * ++ * @since Commons Collections 3.1 ++ */ ++ public static final MapIterator INSTANCE = new EmptyMapIterator(); ++ ++ /** ++ * Constructor. ++ */ ++ protected EmptyMapIterator() { ++ super(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/IterableMap.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/IterableMap.java +new file mode 100644 +index 0000000..251b587 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/IterableMap.java +@@ -0,0 +1,61 @@ ++// GenericsNote: Converted. ++/* ++ * Copyright 2003-2004 The Apache Software Foundation ++ * ++ * 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.collections; ++ ++import java.util.Map; ++ ++/** ++ * Defines a map that can be iterated directly without needing to create an entry set. ++ *

      ++ * A map iterator is an efficient way of iterating over maps. ++ * There is no need to access the entry set or cast to Map Entry objects. ++ *

      ++ * IterableMap map = new HashedMap();
      ++ * MapIterator it = map.mapIterator();
      ++ * while (it.hasNext()) {
      ++ *   Object key = it.next();
      ++ *   Object value = it.getValue();
      ++ *   it.setValue("newValue");
      ++ * }
      ++ * 
      ++ * ++ * @author Matt Hall, John Watkinson, Stephen Colebourne ++ * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:19 $ ++ * @since Commons Collections 3.0 ++ */ ++public interface IterableMap extends Map { ++ ++ /** ++ * Obtains a MapIterator over the map. ++ *

      ++ * A map iterator is an efficient way of iterating over maps. ++ * There is no need to access the entry set or cast to Map Entry objects. ++ *

      ++     * IterableMap map = new HashedMap();
      ++     * MapIterator it = map.mapIterator();
      ++     * while (it.hasNext()) {
      ++     *   Object key = it.next();
      ++     *   Object value = it.getValue();
      ++     *   it.setValue("newValue");
      ++     * }
      ++     * 
      ++ * ++ * @return a map iterator ++ */ ++ MapIterator mapIterator(); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/KeyValue.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/KeyValue.java +new file mode 100644 +index 0000000..c73621d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/KeyValue.java +@@ -0,0 +1,46 @@ ++// GenericsNote: Converted. ++/* ++ * Copyright 2003-2004 The Apache Software Foundation ++ * ++ * 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.collections; ++ ++/** ++ * Defines a simple key value pair. ++ *

      ++ * A Map Entry has considerable additional semantics over and above a simple ++ * key-value pair. This interface defines the minimum key value, with just the ++ * two get methods. ++ * ++ * @author Matt Hall, John Watkinson, Stephen Colebourne ++ * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:19 $ ++ * @since Commons Collections 3.0 ++ */ ++public interface KeyValue { ++ ++ /** ++ * Gets the key from the pair. ++ * ++ * @return the key ++ */ ++ K getKey(); ++ ++ /** ++ * Gets the value from the pair. ++ * ++ * @return the value ++ */ ++ V getValue(); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/MapIterator.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/MapIterator.java +new file mode 100644 +index 0000000..fe2398c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/MapIterator.java +@@ -0,0 +1,109 @@ ++// GenericsNote: Converted. ++/* ++ * Copyright 2003-2004 The Apache Software Foundation ++ * ++ * 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.collections; ++ ++import java.util.Iterator; ++ ++/** ++ * Defines an iterator that operates over a Map. ++ *

      ++ * This iterator is a special version designed for maps. It can be more ++ * efficient to use this rather than an entry set iterator where the option ++ * is available, and it is certainly more convenient. ++ *

      ++ * A map that provides this interface may not hold the data internally using ++ * Map Entry objects, thus this interface can avoid lots of object creation. ++ *

      ++ * In use, this iterator iterates through the keys in the map. After each call ++ * to next(), the getValue() method provides direct ++ * access to the value. The value can also be set using setValue(). ++ *

      ++ * MapIterator it = map.mapIterator();
      ++ * while (it.hasNext()) {
      ++ *   Object key = it.next();
      ++ *   Object value = it.getValue();
      ++ *   it.setValue(newValue);
      ++ * }
      ++ * 
      ++ * ++ * @author Matt Hall, John Watkinson, Stephen Colebourne ++ * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:19 $ ++ * @since Commons Collections 3.0 ++ */ ++public interface MapIterator extends Iterator { ++ ++ /** ++ * Checks to see if there are more entries still to be iterated. ++ * ++ * @return true if the iterator has more elements ++ */ ++ boolean hasNext(); ++ ++ /** ++ * Gets the next key from the Map. ++ * ++ * @return the next key in the iteration ++ * @throws java.util.NoSuchElementException ++ * if the iteration is finished ++ */ ++ K next(); ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Gets the current key, which is the key returned by the last call ++ * to next(). ++ * ++ * @return the current key ++ * @throws IllegalStateException if next() has not yet been called ++ */ ++ K getKey(); ++ ++ /** ++ * Gets the current value, which is the value associated with the last key ++ * returned by next(). ++ * ++ * @return the current value ++ * @throws IllegalStateException if next() has not yet been called ++ */ ++ V getValue(); ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Removes the last returned key from the underlying Map (optional operation). ++ *

      ++ * This method can be called once per call to next(). ++ * ++ * @throws UnsupportedOperationException if remove is not supported by the map ++ * @throws IllegalStateException if next() has not yet been called ++ * @throws IllegalStateException if remove() has already been called ++ * since the last call to next() ++ */ ++ void remove(); ++ ++ /** ++ * Sets the value associated with the current key (optional operation). ++ * ++ * @param value the new value ++ * @return the previous value ++ * @throws UnsupportedOperationException if setValue is not supported by the map ++ * @throws IllegalStateException if next() has not yet been called ++ * @throws IllegalStateException if remove() has been called since the ++ * last call to next() ++ */ ++ V setValue(V value); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/ReferenceMap.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/ReferenceMap.java +new file mode 100644 +index 0000000..f30954d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/ReferenceMap.java +@@ -0,0 +1,161 @@ ++// GenericsNote: Converted. ++/* ++ * Copyright 2002-2004 The Apache Software Foundation ++ * ++ * 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.collections; ++ ++import java.io.IOException; ++import java.io.ObjectInputStream; ++import java.io.ObjectOutputStream; ++import java.io.Serializable; ++ ++/** ++ * A Map implementation that allows mappings to be ++ * removed by the garbage collector. ++ *

      ++ * When you construct a ReferenceMap, you can specify what kind ++ * of references are used to store the map's keys and values. ++ * If non-hard references are used, then the garbage collector can remove ++ * mappings if a key or value becomes unreachable, or if the JVM's memory is ++ * running low. For information on how the different reference types behave, ++ * see {@link java.lang.ref.Reference}. ++ *

      ++ * Different types of references can be specified for keys and values. ++ * The keys can be configured to be weak but the values hard, ++ * in which case this class will behave like a ++ * ++ * WeakHashMap. However, you can also specify hard keys and ++ * weak values, or any other combination. The default constructor uses ++ * hard keys and soft values, providing a memory-sensitive cache. ++ *

      ++ * This map is similar to ReferenceIdentityMap. ++ * It differs in that keys and values in this class are compared using equals(). ++ *

      ++ * This {@link java.util.Map} implementation does not allow null elements. ++ * Attempting to add a null key or value to the map will raise a NullPointerException. ++ *

      ++ * This implementation is not synchronized. ++ * You can use {@link java.util.Collections#synchronizedMap} to ++ * provide synchronized access to a ReferenceMap. ++ * Remember that synchronization will not stop the garbage collecter removing entries. ++ *

      ++ * All the available iterators can be reset back to the start by casting to ++ * ResettableIterator and calling reset(). ++ *

      ++ * NOTE: As from Commons Collections 3.1 this map extends AbstractReferenceMap ++ * (previously it extended AbstractMap). As a result, the implementation is now ++ * extensible and provides a MapIterator. ++ * ++ * @author Paul Jack ++ * @author Matt Hall, John Watkinson, Stephen Colebourne ++ * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:32 $ ++ * @see java.lang.ref.Reference ++ * @since Commons Collections 3.0 (previously in main package v2.1) ++ */ ++public class ReferenceMap extends AbstractReferenceMap implements Serializable { ++ ++ /** ++ * Serialization version ++ */ ++ private static final long serialVersionUID = 1555089888138299607L; ++ ++ /** ++ * Constructs a new ReferenceMap that will ++ * use hard references to keys and soft references to values. ++ */ ++ public ReferenceMap() { ++ super(HARD, SOFT, DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, false); ++ } ++ ++ /** ++ * Constructs a new ReferenceMap that will ++ * use the specified types of references. ++ * ++ * @param keyType the type of reference to use for keys; ++ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK} ++ * @param valueType the type of reference to use for values; ++ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK} ++ */ ++ public ReferenceMap(int keyType, int valueType) { ++ super(keyType, valueType, DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, false); ++ } ++ ++ /** ++ * Constructs a new ReferenceMap that will ++ * use the specified types of references. ++ * ++ * @param keyType the type of reference to use for keys; ++ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK} ++ * @param valueType the type of reference to use for values; ++ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK} ++ * @param purgeValues should the value be automatically purged when the ++ * key is garbage collected ++ */ ++ public ReferenceMap(int keyType, int valueType, boolean purgeValues) { ++ super(keyType, valueType, DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, purgeValues); ++ } ++ ++ /** ++ * Constructs a new ReferenceMap with the ++ * specified reference types, load factor and initial ++ * capacity. ++ * ++ * @param keyType the type of reference to use for keys; ++ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK} ++ * @param valueType the type of reference to use for values; ++ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK} ++ * @param capacity the initial capacity for the map ++ * @param loadFactor the load factor for the map ++ */ ++ public ReferenceMap(int keyType, int valueType, int capacity, float loadFactor) { ++ super(keyType, valueType, capacity, loadFactor, false); ++ } ++ ++ /** ++ * Constructs a new ReferenceMap with the ++ * specified reference types, load factor and initial ++ * capacity. ++ * ++ * @param keyType the type of reference to use for keys; ++ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK} ++ * @param valueType the type of reference to use for values; ++ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK} ++ * @param capacity the initial capacity for the map ++ * @param loadFactor the load factor for the map ++ * @param purgeValues should the value be automatically purged when the ++ * key is garbage collected ++ */ ++ public ReferenceMap(int keyType, int valueType, int capacity, float loadFactor, boolean purgeValues) { ++ super(keyType, valueType, capacity, loadFactor, purgeValues); ++ } ++ ++ //----------------------------------------------------------------------- ++ /** ++ * Write the map out using a custom routine. ++ */ ++ private void writeObject(ObjectOutputStream out) throws IOException { ++ out.defaultWriteObject(); ++ doWriteObject(out); ++ } ++ ++ /** ++ * Read the map in using a custom routine. ++ */ ++ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { ++ in.defaultReadObject(); ++ doReadObject(in); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/ResettableIterator.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/ResettableIterator.java +new file mode 100644 +index 0000000..cf814f7 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/ResettableIterator.java +@@ -0,0 +1,38 @@ ++// GenericsNote: Converted. ++/* ++ * Copyright 2003-2004 The Apache Software Foundation ++ * ++ * 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.collections; ++ ++import java.util.Iterator; ++ ++/** ++ * Defines an iterator that can be reset back to an initial state. ++ *

      ++ * This interface allows an iterator to be repeatedly reused. ++ * ++ * @author Matt Hall, John Watkinson, Stephen Colebourne ++ * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:19 $ ++ * @since Commons Collections 3.0 ++ */ ++public interface ResettableIterator extends Iterator { ++ ++ /** ++ * Resets the iterator back to the position at which the iterator ++ * was created. ++ */ ++ public void reset(); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/package.html +new file mode 100644 +index 0000000..e34bfe3 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/package.html +@@ -0,0 +1 @@ ++Utility classes. +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/ChatState.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ChatState.java +new file mode 100644 +index 0000000..4acaa49 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ChatState.java +@@ -0,0 +1,50 @@ ++/** ++ * $RCSfile$ ++ * $Revision: 2407 $ ++ * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++/** ++ * Represents the current state of a users interaction with another user. Implemented according to ++ * XEP-0085. ++ * ++ * @author Alexander Wenckus ++ */ ++public enum ChatState { ++ /** ++ * User is actively participating in the chat session. ++ */ ++ active, ++ /** ++ * User is composing a message. ++ */ ++ composing, ++ /** ++ * User had been composing but now has stopped. ++ */ ++ paused, ++ /** ++ * User has not been actively participating in the chat session. ++ */ ++ inactive, ++ /** ++ * User has effectively ended their participation in the chat session. ++ */ ++ gone ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/ChatStateListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ChatStateListener.java +new file mode 100644 +index 0000000..9a1bf79 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ChatStateListener.java +@@ -0,0 +1,40 @@ ++/** ++ * $RCSfile$ ++ * $Revision: 2407 $ ++ * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import org.jivesoftware.smack.Chat; ++import org.jivesoftware.smack.MessageListener; ++ ++/** ++ * Events for when the state of a user in a chat changes. ++ * ++ * @author Alexander Wenckus ++ */ ++public interface ChatStateListener extends MessageListener { ++ ++ /** ++ * Fired when the state of a chat with another user changes. ++ * ++ * @param chat the chat in which the state has changed. ++ * @param state the new state of the participant. ++ */ ++ void stateChanged(Chat chat, ChatState state); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/ChatStateManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ChatStateManager.java +new file mode 100644 +index 0000000..d452a9f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ChatStateManager.java +@@ -0,0 +1,200 @@ ++/** ++ * $RCSfile$ ++ * $Revision: 2407 $ ++ * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import org.jivesoftware.smack.*; ++import org.jivesoftware.smack.util.collections.ReferenceMap; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.NotFilter; ++import org.jivesoftware.smack.filter.PacketExtensionFilter; ++import org.jivesoftware.smack.packet.Message; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.packet.ChatStateExtension; ++ ++import java.util.Map; ++import java.util.WeakHashMap; ++ ++/** ++ * Handles chat state for all chats on a particular Connection. This class manages both the ++ * packet extensions and the disco response neccesary for compliance with ++ * XEP-0085. ++ * ++ * NOTE: {@link org.jivesoftware.smackx.ChatStateManager#getInstance(org.jivesoftware.smack.Connection)} ++ * needs to be called in order for the listeners to be registered appropriately with the connection. ++ * If this does not occur you will not receive the update notifications. ++ * ++ * @author Alexander Wenckus ++ * @see org.jivesoftware.smackx.ChatState ++ * @see org.jivesoftware.smackx.packet.ChatStateExtension ++ */ ++public class ChatStateManager { ++ ++ private static final Map managers = ++ new WeakHashMap(); ++ ++ private static final PacketFilter filter = new NotFilter( ++ new PacketExtensionFilter("http://jabber.org/protocol/chatstates")); ++ ++ /** ++ * Returns the ChatStateManager related to the Connection and it will create one if it does ++ * not yet exist. ++ * ++ * @param connection the connection to return the ChatStateManager ++ * @return the ChatStateManager related the the connection. ++ */ ++ public static ChatStateManager getInstance(final Connection connection) { ++ if(connection == null) { ++ return null; ++ } ++ synchronized (managers) { ++ ChatStateManager manager = managers.get(connection); ++ if (manager == null) { ++ manager = new ChatStateManager(connection); ++ manager.init(); ++ managers.put(connection, manager); ++ } ++ ++ return manager; ++ } ++ } ++ ++ private final Connection connection; ++ ++ private final OutgoingMessageInterceptor outgoingInterceptor = new OutgoingMessageInterceptor(); ++ ++ private final IncomingMessageInterceptor incomingInterceptor = new IncomingMessageInterceptor(); ++ ++ /** ++ * Maps chat to last chat state. ++ */ ++ private final Map chatStates = ++ new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.HARD); ++ ++ private ChatStateManager(Connection connection) { ++ this.connection = connection; ++ } ++ ++ private void init() { ++ connection.getChatManager().addOutgoingMessageInterceptor(outgoingInterceptor, ++ filter); ++ connection.getChatManager().addChatListener(incomingInterceptor); ++ ++ ServiceDiscoveryManager.getInstanceFor(connection) ++ .addFeature("http://jabber.org/protocol/chatstates"); ++ } ++ ++ /** ++ * Sets the current state of the provided chat. This method will send an empty bodied Message ++ * packet with the state attached as a {@link org.jivesoftware.smack.packet.PacketExtension}, if ++ * and only if the new chat state is different than the last state. ++ * ++ * @param newState the new state of the chat ++ * @param chat the chat. ++ * @throws org.jivesoftware.smack.XMPPException ++ * when there is an error sending the message ++ * packet. ++ */ ++ public void setCurrentState(ChatState newState, Chat chat) throws XMPPException { ++ if(chat == null || newState == null) { ++ throw new IllegalArgumentException("Arguments cannot be null."); ++ } ++ if(!updateChatState(chat, newState)) { ++ return; ++ } ++ Message message = new Message(); ++ ChatStateExtension extension = new ChatStateExtension(newState); ++ message.addExtension(extension); ++ ++ chat.sendMessage(message); ++ } ++ ++ ++ public boolean equals(Object o) { ++ if (this == o) return true; ++ if (o == null || getClass() != o.getClass()) return false; ++ ++ ChatStateManager that = (ChatStateManager) o; ++ ++ return connection.equals(that.connection); ++ ++ } ++ ++ public int hashCode() { ++ return connection.hashCode(); ++ } ++ ++ private boolean updateChatState(Chat chat, ChatState newState) { ++ ChatState lastChatState = chatStates.get(chat); ++ if (lastChatState != newState) { ++ chatStates.put(chat, newState); ++ return true; ++ } ++ return false; ++ } ++ ++ private void fireNewChatState(Chat chat, ChatState state) { ++ for (MessageListener listener : chat.getListeners()) { ++ if (listener instanceof ChatStateListener) { ++ ((ChatStateListener) listener).stateChanged(chat, state); ++ } ++ } ++ } ++ ++ private class OutgoingMessageInterceptor implements PacketInterceptor { ++ ++ public void interceptPacket(Packet packet) { ++ Message message = (Message) packet; ++ Chat chat = connection.getChatManager().getThreadChat(message.getThread()); ++ if (chat == null) { ++ return; ++ } ++ if (updateChatState(chat, ChatState.active)) { ++ message.addExtension(new ChatStateExtension(ChatState.active)); ++ } ++ } ++ } ++ ++ private class IncomingMessageInterceptor implements ChatManagerListener, MessageListener { ++ ++ public void chatCreated(final Chat chat, boolean createdLocally) { ++ chat.addMessageListener(this); ++ } ++ ++ public void processMessage(Chat chat, Message message) { ++ PacketExtension extension ++ = message.getExtension("http://jabber.org/protocol/chatstates"); ++ if (extension == null) { ++ return; ++ } ++ ++ ChatState state; ++ try { ++ state = ChatState.valueOf(extension.getElementName()); ++ } ++ catch (Exception ex) { ++ return; ++ } ++ ++ fireNewChatState(chat, state); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/DefaultMessageEventRequestListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/DefaultMessageEventRequestListener.java +new file mode 100644 +index 0000000..7ae0c51 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/DefaultMessageEventRequestListener.java +@@ -0,0 +1,55 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++/** ++ * ++ * Default implementation of the MessageEventRequestListener interface.

      ++ * ++ * This class automatically sends a delivered notification to the sender of the message ++ * if the sender has requested to be notified when the message is delivered. ++ * ++ * @author Gaston Dombiak ++ */ ++public class DefaultMessageEventRequestListener implements MessageEventRequestListener { ++ ++ public void deliveredNotificationRequested(String from, String packetID, ++ MessageEventManager messageEventManager) ++ { ++ // Send to the message's sender that the message has been delivered ++ messageEventManager.sendDeliveredNotification(from, packetID); ++ } ++ ++ public void displayedNotificationRequested(String from, String packetID, ++ MessageEventManager messageEventManager) ++ { ++ } ++ ++ public void composingNotificationRequested(String from, String packetID, ++ MessageEventManager messageEventManager) ++ { ++ } ++ ++ public void offlineNotificationRequested(String from, String packetID, ++ MessageEventManager messageEventManager) ++ { ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/Form.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/Form.java +new file mode 100644 +index 0000000..ec6dde4 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/Form.java +@@ -0,0 +1,545 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.packet.DataForm; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++import java.util.StringTokenizer; ++ ++/** ++ * Represents a Form for gathering data. The form could be of the following types: ++ *

        ++ *
      • form -> Indicates a form to fill out.
      • ++ *
      • submit -> The form is filled out, and this is the data that is being returned from ++ * the form.
      • ++ *
      • cancel -> The form was cancelled. Tell the asker that piece of information.
      • ++ *
      • result -> Data results being returned from a search, or some other query.
      • ++ *
      ++ * ++ * Depending of the form's type different operations are available. For example, it's only possible ++ * to set answers if the form is of type "submit". ++ * ++ * @author Gaston Dombiak ++ */ ++public class Form { ++ ++ public static final String TYPE_FORM = "form"; ++ public static final String TYPE_SUBMIT = "submit"; ++ public static final String TYPE_CANCEL = "cancel"; ++ public static final String TYPE_RESULT = "result"; ++ ++ private DataForm dataForm; ++ ++ /** ++ * Returns a new ReportedData if the packet is used for gathering data and includes an ++ * extension that matches the elementName and namespace "x","jabber:x:data". ++ * ++ * @param packet the packet used for gathering data. ++ * @return the data form parsed from the packet or null if there was not ++ * a form in the packet. ++ */ ++ public static Form getFormFrom(Packet packet) { ++ // Check if the packet includes the DataForm extension ++ PacketExtension packetExtension = packet.getExtension("x","jabber:x:data"); ++ if (packetExtension != null) { ++ // Check if the existing DataForm is not a result of a search ++ DataForm dataForm = (DataForm) packetExtension; ++ if (dataForm.getReportedData() == null) ++ return new Form(dataForm); ++ } ++ // Otherwise return null ++ return null; ++ } ++ ++ /** ++ * Creates a new Form that will wrap an existing DataForm. The wrapped DataForm must be ++ * used for gathering data. ++ * ++ * @param dataForm the data form used for gathering data. ++ */ ++ public Form(DataForm dataForm) { ++ this.dataForm = dataForm; ++ } ++ ++ /** ++ * Creates a new Form of a given type from scratch.

      ++ * ++ * Possible form types are: ++ *

        ++ *
      • form -> Indicates a form to fill out.
      • ++ *
      • submit -> The form is filled out, and this is the data that is being returned from ++ * the form.
      • ++ *
      • cancel -> The form was cancelled. Tell the asker that piece of information.
      • ++ *
      • result -> Data results being returned from a search, or some other query.
      • ++ *
      ++ * ++ * @param type the form's type (e.g. form, submit,cancel,result). ++ */ ++ public Form(String type) { ++ this.dataForm = new DataForm(type); ++ } ++ ++ /** ++ * Adds a new field to complete as part of the form. ++ * ++ * @param field the field to complete. ++ */ ++ public void addField(FormField field) { ++ dataForm.addField(field); ++ } ++ ++ /** ++ * Sets a new String value to a given form's field. The field whose variable matches the ++ * requested variable will be completed with the specified value. If no field could be found ++ * for the specified variable then an exception will be raised.

      ++ * ++ * If the value to set to the field is not a basic type (e.g. String, boolean, int, etc.) you ++ * can use this message where the String value is the String representation of the object. ++ * ++ * @param variable the variable name that was completed. ++ * @param value the String value that was answered. ++ * @throws IllegalStateException if the form is not of type "submit". ++ * @throws IllegalArgumentException if the form does not include the specified variable or ++ * if the answer type does not correspond with the field type.. ++ */ ++ public void setAnswer(String variable, String value) { ++ FormField field = getField(variable); ++ if (field == null) { ++ throw new IllegalArgumentException("Field not found for the specified variable name."); ++ } ++ if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) ++ && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) ++ && !FormField.TYPE_TEXT_SINGLE.equals(field.getType()) ++ && !FormField.TYPE_JID_SINGLE.equals(field.getType()) ++ && !FormField.TYPE_HIDDEN.equals(field.getType())) { ++ throw new IllegalArgumentException("This field is not of type String."); ++ } ++ setAnswer(field, value); ++ } ++ ++ /** ++ * Sets a new int value to a given form's field. The field whose variable matches the ++ * requested variable will be completed with the specified value. If no field could be found ++ * for the specified variable then an exception will be raised. ++ * ++ * @param variable the variable name that was completed. ++ * @param value the int value that was answered. ++ * @throws IllegalStateException if the form is not of type "submit". ++ * @throws IllegalArgumentException if the form does not include the specified variable or ++ * if the answer type does not correspond with the field type. ++ */ ++ public void setAnswer(String variable, int value) { ++ FormField field = getField(variable); ++ if (field == null) { ++ throw new IllegalArgumentException("Field not found for the specified variable name."); ++ } ++ if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) ++ && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) ++ && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { ++ throw new IllegalArgumentException("This field is not of type int."); ++ } ++ setAnswer(field, value); ++ } ++ ++ /** ++ * Sets a new long value to a given form's field. The field whose variable matches the ++ * requested variable will be completed with the specified value. If no field could be found ++ * for the specified variable then an exception will be raised. ++ * ++ * @param variable the variable name that was completed. ++ * @param value the long value that was answered. ++ * @throws IllegalStateException if the form is not of type "submit". ++ * @throws IllegalArgumentException if the form does not include the specified variable or ++ * if the answer type does not correspond with the field type. ++ */ ++ public void setAnswer(String variable, long value) { ++ FormField field = getField(variable); ++ if (field == null) { ++ throw new IllegalArgumentException("Field not found for the specified variable name."); ++ } ++ if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) ++ && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) ++ && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { ++ throw new IllegalArgumentException("This field is not of type long."); ++ } ++ setAnswer(field, value); ++ } ++ ++ /** ++ * Sets a new float value to a given form's field. The field whose variable matches the ++ * requested variable will be completed with the specified value. If no field could be found ++ * for the specified variable then an exception will be raised. ++ * ++ * @param variable the variable name that was completed. ++ * @param value the float value that was answered. ++ * @throws IllegalStateException if the form is not of type "submit". ++ * @throws IllegalArgumentException if the form does not include the specified variable or ++ * if the answer type does not correspond with the field type. ++ */ ++ public void setAnswer(String variable, float value) { ++ FormField field = getField(variable); ++ if (field == null) { ++ throw new IllegalArgumentException("Field not found for the specified variable name."); ++ } ++ if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) ++ && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) ++ && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { ++ throw new IllegalArgumentException("This field is not of type float."); ++ } ++ setAnswer(field, value); ++ } ++ ++ /** ++ * Sets a new double value to a given form's field. The field whose variable matches the ++ * requested variable will be completed with the specified value. If no field could be found ++ * for the specified variable then an exception will be raised. ++ * ++ * @param variable the variable name that was completed. ++ * @param value the double value that was answered. ++ * @throws IllegalStateException if the form is not of type "submit". ++ * @throws IllegalArgumentException if the form does not include the specified variable or ++ * if the answer type does not correspond with the field type. ++ */ ++ public void setAnswer(String variable, double value) { ++ FormField field = getField(variable); ++ if (field == null) { ++ throw new IllegalArgumentException("Field not found for the specified variable name."); ++ } ++ if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) ++ && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) ++ && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { ++ throw new IllegalArgumentException("This field is not of type double."); ++ } ++ setAnswer(field, value); ++ } ++ ++ /** ++ * Sets a new boolean value to a given form's field. The field whose variable matches the ++ * requested variable will be completed with the specified value. If no field could be found ++ * for the specified variable then an exception will be raised. ++ * ++ * @param variable the variable name that was completed. ++ * @param value the boolean value that was answered. ++ * @throws IllegalStateException if the form is not of type "submit". ++ * @throws IllegalArgumentException if the form does not include the specified variable or ++ * if the answer type does not correspond with the field type. ++ */ ++ public void setAnswer(String variable, boolean value) { ++ FormField field = getField(variable); ++ if (field == null) { ++ throw new IllegalArgumentException("Field not found for the specified variable name."); ++ } ++ if (!FormField.TYPE_BOOLEAN.equals(field.getType())) { ++ throw new IllegalArgumentException("This field is not of type boolean."); ++ } ++ setAnswer(field, (value ? "1" : "0")); ++ } ++ ++ /** ++ * Sets a new Object value to a given form's field. In fact, the object representation ++ * (i.e. #toString) will be the actual value of the field.

      ++ * ++ * If the value to set to the field is not a basic type (e.g. String, boolean, int, etc.) you ++ * will need to use {@link #setAnswer(String, String))} where the String value is the ++ * String representation of the object.

      ++ * ++ * Before setting the new value to the field we will check if the form is of type submit. If ++ * the form isn't of type submit means that it's not possible to complete the form and an ++ * exception will be thrown. ++ * ++ * @param field the form field that was completed. ++ * @param value the Object value that was answered. The object representation will be the ++ * actual value. ++ * @throws IllegalStateException if the form is not of type "submit". ++ */ ++ private void setAnswer(FormField field, Object value) { ++ if (!isSubmitType()) { ++ throw new IllegalStateException("Cannot set an answer if the form is not of type " + ++ "\"submit\""); ++ } ++ field.resetValues(); ++ field.addValue(value.toString()); ++ } ++ ++ /** ++ * Sets a new values to a given form's field. The field whose variable matches the requested ++ * variable will be completed with the specified values. If no field could be found for ++ * the specified variable then an exception will be raised.

      ++ * ++ * The Objects contained in the List could be of any type. The String representation of them ++ * (i.e. #toString) will be actually used when sending the answer to the server. ++ * ++ * @param variable the variable that was completed. ++ * @param values the values that were answered. ++ * @throws IllegalStateException if the form is not of type "submit". ++ * @throws IllegalArgumentException if the form does not include the specified variable. ++ */ ++ public void setAnswer(String variable, List values) { ++ if (!isSubmitType()) { ++ throw new IllegalStateException("Cannot set an answer if the form is not of type " + ++ "\"submit\""); ++ } ++ FormField field = getField(variable); ++ if (field != null) { ++ // Check that the field can accept a collection of values ++ if (!FormField.TYPE_JID_MULTI.equals(field.getType()) ++ && !FormField.TYPE_LIST_MULTI.equals(field.getType()) ++ && !FormField.TYPE_LIST_SINGLE.equals(field.getType()) ++ && !FormField.TYPE_HIDDEN.equals(field.getType())) { ++ throw new IllegalArgumentException("This field only accept list of values."); ++ } ++ // Clear the old values ++ field.resetValues(); ++ // Set the new values. The string representation of each value will be actually used. ++ field.addValues(values); ++ } ++ else { ++ throw new IllegalArgumentException("Couldn't find a field for the specified variable."); ++ } ++ } ++ ++ /** ++ * Sets the default value as the value of a given form's field. The field whose variable matches ++ * the requested variable will be completed with its default value. If no field could be found ++ * for the specified variable then an exception will be raised. ++ * ++ * @param variable the variable to complete with its default value. ++ * @throws IllegalStateException if the form is not of type "submit". ++ * @throws IllegalArgumentException if the form does not include the specified variable. ++ */ ++ public void setDefaultAnswer(String variable) { ++ if (!isSubmitType()) { ++ throw new IllegalStateException("Cannot set an answer if the form is not of type " + ++ "\"submit\""); ++ } ++ FormField field = getField(variable); ++ if (field != null) { ++ // Clear the old values ++ field.resetValues(); ++ // Set the default value ++ for (Iterator it = field.getValues(); it.hasNext();) { ++ field.addValue(it.next()); ++ } ++ } ++ else { ++ throw new IllegalArgumentException("Couldn't find a field for the specified variable."); ++ } ++ } ++ ++ /** ++ * Returns an Iterator for the fields that are part of the form. ++ * ++ * @return an Iterator for the fields that are part of the form. ++ */ ++ public Iterator getFields() { ++ return dataForm.getFields(); ++ } ++ ++ /** ++ * Returns the field of the form whose variable matches the specified variable. ++ * The fields of type FIXED will never be returned since they do not specify a ++ * variable. ++ * ++ * @param variable the variable to look for in the form fields. ++ * @return the field of the form whose variable matches the specified variable. ++ */ ++ public FormField getField(String variable) { ++ if (variable == null || variable.equals("")) { ++ throw new IllegalArgumentException("Variable must not be null or blank."); ++ } ++ // Look for the field whose variable matches the requested variable ++ FormField field; ++ for (Iterator it=getFields();it.hasNext();) { ++ field = it.next(); ++ if (variable.equals(field.getVariable())) { ++ return field; ++ } ++ } ++ return null; ++ } ++ ++ /** ++ * Returns the instructions that explain how to fill out the form and what the form is about. ++ * ++ * @return instructions that explain how to fill out the form. ++ */ ++ public String getInstructions() { ++ StringBuilder sb = new StringBuilder(); ++ // Join the list of instructions together separated by newlines ++ for (Iterator it = dataForm.getInstructions(); it.hasNext();) { ++ sb.append((String) it.next()); ++ // If this is not the last instruction then append a newline ++ if (it.hasNext()) { ++ sb.append("\n"); ++ } ++ } ++ return sb.toString(); ++ } ++ ++ ++ /** ++ * Returns the description of the data. It is similar to the title on a web page or an X ++ * window. You can put a on either a form to fill out, or a set of data results. ++ * ++ * @return description of the data. ++ */ ++ public String getTitle() { ++ return dataForm.getTitle(); ++ } ++ ++ ++ /** ++ * Returns the meaning of the data within the context. The data could be part of a form ++ * to fill out, a form submission or data results.<p> ++ * ++ * Possible form types are: ++ * <ul> ++ * <li>form -> Indicates a form to fill out.</li> ++ * <li>submit -> The form is filled out, and this is the data that is being returned from ++ * the form.</li> ++ * <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li> ++ * <li>result -> Data results being returned from a search, or some other query.</li> ++ * </ul> ++ * ++ * @return the form's type. ++ */ ++ public String getType() { ++ return dataForm.getType(); ++ } ++ ++ ++ /** ++ * Sets instructions that explain how to fill out the form and what the form is about. ++ * ++ * @param instructions instructions that explain how to fill out the form. ++ */ ++ public void setInstructions(String instructions) { ++ // Split the instructions into multiple instructions for each existent newline ++ ArrayList<String> instructionsList = new ArrayList<String>(); ++ StringTokenizer st = new StringTokenizer(instructions, "\n"); ++ while (st.hasMoreTokens()) { ++ instructionsList.add(st.nextToken()); ++ } ++ // Set the new list of instructions ++ dataForm.setInstructions(instructionsList); ++ ++ } ++ ++ ++ /** ++ * Sets the description of the data. It is similar to the title on a web page or an X window. ++ * You can put a <title/> on either a form to fill out, or a set of data results. ++ * ++ * @param title description of the data. ++ */ ++ public void setTitle(String title) { ++ dataForm.setTitle(title); ++ } ++ ++ /** ++ * Returns a DataForm that serves to send this Form to the server. If the form is of type ++ * submit, it may contain fields with no value. These fields will be removed since they only ++ * exist to assist the user while editing/completing the form in a UI. ++ * ++ * @return the wrapped DataForm. ++ */ ++ public DataForm getDataFormToSend() { ++ if (isSubmitType()) { ++ // Create a new DataForm that contains only the answered fields ++ DataForm dataFormToSend = new DataForm(getType()); ++ for(Iterator<FormField> it=getFields();it.hasNext();) { ++ FormField field = it.next(); ++ if (field.getValues().hasNext()) { ++ dataFormToSend.addField(field); ++ } ++ } ++ return dataFormToSend; ++ } ++ return dataForm; ++ } ++ ++ /** ++ * Returns true if the form is a form to fill out. ++ * ++ * @return if the form is a form to fill out. ++ */ ++ private boolean isFormType() { ++ return TYPE_FORM.equals(dataForm.getType()); ++ } ++ ++ /** ++ * Returns true if the form is a form to submit. ++ * ++ * @return if the form is a form to submit. ++ */ ++ private boolean isSubmitType() { ++ return TYPE_SUBMIT.equals(dataForm.getType()); ++ } ++ ++ /** ++ * Returns a new Form to submit the completed values. The new Form will include all the fields ++ * of the original form except for the fields of type FIXED. Only the HIDDEN fields will ++ * include the same value of the original form. The other fields of the new form MUST be ++ * completed. If a field remains with no answer when sending the completed form, then it won't ++ * be included as part of the completed form.<p> ++ * ++ * The reason why the fields with variables are included in the new form is to provide a model ++ * for binding with any UI. This means that the UIs will use the original form (of type ++ * "form") to learn how to render the form, but the UIs will bind the fields to the form of ++ * type submit. ++ * ++ * @return a Form to submit the completed values. ++ */ ++ public Form createAnswerForm() { ++ if (!isFormType()) { ++ throw new IllegalStateException("Only forms of type \"form\" could be answered"); ++ } ++ // Create a new Form ++ Form form = new Form(TYPE_SUBMIT); ++ for (Iterator<FormField> fields=getFields(); fields.hasNext();) { ++ FormField field = fields.next(); ++ // Add to the new form any type of field that includes a variable. ++ // Note: The fields of type FIXED are the only ones that don't specify a variable ++ if (field.getVariable() != null) { ++ FormField newField = new FormField(field.getVariable()); ++ newField.setType(field.getType()); ++ form.addField(newField); ++ // Set the answer ONLY to the hidden fields ++ if (FormField.TYPE_HIDDEN.equals(field.getType())) { ++ // Since a hidden field could have many values we need to collect them ++ // in a list ++ List<String> values = new ArrayList<String>(); ++ for (Iterator<String> it=field.getValues();it.hasNext();) { ++ values.add(it.next()); ++ } ++ form.setAnswer(field.getVariable(), values); ++ } ++ } ++ } ++ return form; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/FormField.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/FormField.java +new file mode 100644 +index 0000000..2f2c00e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/FormField.java +@@ -0,0 +1,358 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import org.jivesoftware.smack.util.StringUtils; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++ ++/** ++ * Represents a field of a form. The field could be used to represent a question to complete, ++ * a completed question or a data returned from a search. The exact interpretation of the field ++ * depends on the context where the field is used. ++ * ++ * @author Gaston Dombiak ++ */ ++public class FormField { ++ ++ public static final String TYPE_BOOLEAN = "boolean"; ++ public static final String TYPE_FIXED = "fixed"; ++ public static final String TYPE_HIDDEN = "hidden"; ++ public static final String TYPE_JID_MULTI = "jid-multi"; ++ public static final String TYPE_JID_SINGLE = "jid-single"; ++ public static final String TYPE_LIST_MULTI = "list-multi"; ++ public static final String TYPE_LIST_SINGLE = "list-single"; ++ public static final String TYPE_TEXT_MULTI = "text-multi"; ++ public static final String TYPE_TEXT_PRIVATE = "text-private"; ++ public static final String TYPE_TEXT_SINGLE = "text-single"; ++ ++ private String description; ++ private boolean required = false; ++ private String label; ++ private String variable; ++ private String type; ++ private final List<Option> options = new ArrayList<Option>(); ++ private final List<String> values = new ArrayList<String>(); ++ ++ /** ++ * Creates a new FormField with the variable name that uniquely identifies the field ++ * in the context of the form. ++ * ++ * @param variable the variable name of the question. ++ */ ++ public FormField(String variable) { ++ this.variable = variable; ++ } ++ ++ /** ++ * Creates a new FormField of type FIXED. The fields of type FIXED do not define a variable ++ * name. ++ */ ++ public FormField() { ++ this.type = FormField.TYPE_FIXED; ++ } ++ ++ /** ++ * Returns a description that provides extra clarification about the question. This information ++ * could be presented to the user either in tool-tip, help button, or as a section of text ++ * before the question.<p> ++ * <p/> ++ * If the question is of type FIXED then the description should remain empty. ++ * ++ * @return description that provides extra clarification about the question. ++ */ ++ public String getDescription() { ++ return description; ++ } ++ ++ /** ++ * Returns the label of the question which should give enough information to the user to ++ * fill out the form. ++ * ++ * @return label of the question. ++ */ ++ public String getLabel() { ++ return label; ++ } ++ ++ /** ++ * Returns an Iterator for the available options that the user has in order to answer ++ * the question. ++ * ++ * @return Iterator for the available options. ++ */ ++ public Iterator<Option> getOptions() { ++ synchronized (options) { ++ return Collections.unmodifiableList(new ArrayList<Option>(options)).iterator(); ++ } ++ } ++ ++ /** ++ * Returns true if the question must be answered in order to complete the questionnaire. ++ * ++ * @return true if the question must be answered in order to complete the questionnaire. ++ */ ++ public boolean isRequired() { ++ return required; ++ } ++ ++ /** ++ * Returns an indicative of the format for the data to answer. Valid formats are: ++ * <p/> ++ * <ul> ++ * <li>text-single -> single line or word of text ++ * <li>text-private -> instead of showing the user what they typed, you show ***** to ++ * protect it ++ * <li>text-multi -> multiple lines of text entry ++ * <li>list-single -> given a list of choices, pick one ++ * <li>list-multi -> given a list of choices, pick one or more ++ * <li>boolean -> 0 or 1, true or false, yes or no. Default value is 0 ++ * <li>fixed -> fixed for putting in text to show sections, or just advertise your web ++ * site in the middle of the form ++ * <li>hidden -> is not given to the user at all, but returned with the questionnaire ++ * <li>jid-single -> Jabber ID - choosing a JID from your roster, and entering one based ++ * on the rules for a JID. ++ * <li>jid-multi -> multiple entries for JIDs ++ * </ul> ++ * ++ * @return format for the data to answer. ++ */ ++ public String getType() { ++ return type; ++ } ++ ++ /** ++ * Returns an Iterator for the default values of the question if the question is part ++ * of a form to fill out. Otherwise, returns an Iterator for the answered values of ++ * the question. ++ * ++ * @return an Iterator for the default values or answered values of the question. ++ */ ++ public Iterator<String> getValues() { ++ synchronized (values) { ++ return Collections.unmodifiableList(new ArrayList<String>(values)).iterator(); ++ } ++ } ++ ++ /** ++ * Returns the variable name that the question is filling out. ++ * ++ * @return the variable name of the question. ++ */ ++ public String getVariable() { ++ return variable; ++ } ++ ++ /** ++ * Sets a description that provides extra clarification about the question. This information ++ * could be presented to the user either in tool-tip, help button, or as a section of text ++ * before the question.<p> ++ * <p/> ++ * If the question is of type FIXED then the description should remain empty. ++ * ++ * @param description provides extra clarification about the question. ++ */ ++ public void setDescription(String description) { ++ this.description = description; ++ } ++ ++ /** ++ * Sets the label of the question which should give enough information to the user to ++ * fill out the form. ++ * ++ * @param label the label of the question. ++ */ ++ public void setLabel(String label) { ++ this.label = label; ++ } ++ ++ /** ++ * Sets if the question must be answered in order to complete the questionnaire. ++ * ++ * @param required if the question must be answered in order to complete the questionnaire. ++ */ ++ public void setRequired(boolean required) { ++ this.required = required; ++ } ++ ++ /** ++ * Sets an indicative of the format for the data to answer. Valid formats are: ++ * <p/> ++ * <ul> ++ * <li>text-single -> single line or word of text ++ * <li>text-private -> instead of showing the user what they typed, you show ***** to ++ * protect it ++ * <li>text-multi -> multiple lines of text entry ++ * <li>list-single -> given a list of choices, pick one ++ * <li>list-multi -> given a list of choices, pick one or more ++ * <li>boolean -> 0 or 1, true or false, yes or no. Default value is 0 ++ * <li>fixed -> fixed for putting in text to show sections, or just advertise your web ++ * site in the middle of the form ++ * <li>hidden -> is not given to the user at all, but returned with the questionnaire ++ * <li>jid-single -> Jabber ID - choosing a JID from your roster, and entering one based ++ * on the rules for a JID. ++ * <li>jid-multi -> multiple entries for JIDs ++ * </ul> ++ * ++ * @param type an indicative of the format for the data to answer. ++ */ ++ public void setType(String type) { ++ this.type = type; ++ } ++ ++ /** ++ * Adds a default value to the question if the question is part of a form to fill out. ++ * Otherwise, adds an answered value to the question. ++ * ++ * @param value a default value or an answered value of the question. ++ */ ++ public void addValue(String value) { ++ synchronized (values) { ++ values.add(value); ++ } ++ } ++ ++ /** ++ * Adds a default values to the question if the question is part of a form to fill out. ++ * Otherwise, adds an answered values to the question. ++ * ++ * @param newValues default values or an answered values of the question. ++ */ ++ public void addValues(List<String> newValues) { ++ synchronized (values) { ++ values.addAll(newValues); ++ } ++ } ++ ++ /** ++ * Removes all the values of the field. ++ */ ++ protected void resetValues() { ++ synchronized (values) { ++ values.removeAll(new ArrayList<String>(values)); ++ } ++ } ++ ++ /** ++ * Adss an available options to the question that the user has in order to answer ++ * the question. ++ * ++ * @param option a new available option for the question. ++ */ ++ public void addOption(Option option) { ++ synchronized (options) { ++ options.add(option); ++ } ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<field"); ++ // Add attributes ++ if (getLabel() != null) { ++ buf.append(" label=\"").append(getLabel()).append("\""); ++ } ++ if (getVariable() != null) { ++ buf.append(" var=\"").append(getVariable()).append("\""); ++ } ++ if (getType() != null) { ++ buf.append(" type=\"").append(getType()).append("\""); ++ } ++ buf.append(">"); ++ // Add elements ++ if (getDescription() != null) { ++ buf.append("<desc>").append(getDescription()).append("</desc>"); ++ } ++ if (isRequired()) { ++ buf.append("<required/>"); ++ } ++ // Loop through all the values and append them to the string buffer ++ for (Iterator<String> i = getValues(); i.hasNext();) { ++ buf.append("<value>").append(i.next()).append("</value>"); ++ } ++ // Loop through all the values and append them to the string buffer ++ for (Iterator i = getOptions(); i.hasNext();) { ++ buf.append(((Option) i.next()).toXML()); ++ } ++ buf.append("</field>"); ++ return buf.toString(); ++ } ++ ++ /** ++ * Represents the available option of a given FormField. ++ * ++ * @author Gaston Dombiak ++ */ ++ public static class Option { ++ ++ private String label; ++ private String value; ++ ++ public Option(String value) { ++ this.value = value; ++ } ++ ++ public Option(String label, String value) { ++ this.label = label; ++ this.value = value; ++ } ++ ++ /** ++ * Returns the label that represents the option. ++ * ++ * @return the label that represents the option. ++ */ ++ public String getLabel() { ++ return label; ++ } ++ ++ /** ++ * Returns the value of the option. ++ * ++ * @return the value of the option. ++ */ ++ public String getValue() { ++ return value; ++ } ++ ++ public String toString() { ++ return getLabel(); ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<option"); ++ // Add attribute ++ if (getLabel() != null) { ++ buf.append(" label=\"").append(getLabel()).append("\""); ++ } ++ buf.append(">"); ++ // Add element ++ buf.append("<value>").append(StringUtils.escapeForXML(getValue())).append("</value>"); ++ ++ buf.append("</option>"); ++ return buf.toString(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/Gateway.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/Gateway.java +new file mode 100644 +index 0000000..5b5836f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/Gateway.java +@@ -0,0 +1,333 @@ ++package org.jivesoftware.smackx; ++ ++import java.util.HashMap; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.PacketCollector; ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.Roster; ++import org.jivesoftware.smack.RosterEntry; ++import org.jivesoftware.smack.SmackConfiguration; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.Presence; ++import org.jivesoftware.smack.packet.Registration; ++import org.jivesoftware.smack.util.StringUtils; ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++import org.jivesoftware.smackx.packet.DiscoverInfo.Identity; ++ ++/** ++ * This class provides an abstract view to gateways/transports. This class handles all ++ * actions regarding gateways and transports. ++ * @author Till Klocke ++ * ++ */ ++public class Gateway { ++ ++ private Connection connection; ++ private ServiceDiscoveryManager sdManager; ++ private Roster roster; ++ private String entityJID; ++ private Registration registerInfo; ++ private Identity identity; ++ private DiscoverInfo info; ++ ++ Gateway(Connection connection, String entityJID){ ++ this.connection = connection; ++ this.roster = connection.getRoster(); ++ this.sdManager = ServiceDiscoveryManager.getInstanceFor(connection); ++ this.entityJID = entityJID; ++ } ++ ++ Gateway(Connection connection, String entityJID, DiscoverInfo info, Identity identity){ ++ this(connection, entityJID); ++ this.info = info; ++ this.identity = identity; ++ } ++ ++ private void discoverInfo() throws XMPPException{ ++ info = sdManager.discoverInfo(entityJID); ++ Iterator<Identity> iterator = info.getIdentities(); ++ while(iterator.hasNext()){ ++ Identity temp = iterator.next(); ++ if(temp.getCategory().equalsIgnoreCase("gateway")){ ++ this.identity = temp; ++ break; ++ } ++ } ++ } ++ ++ private Identity getIdentity() throws XMPPException{ ++ if(identity==null){ ++ discoverInfo(); ++ } ++ return identity; ++ } ++ ++ private Registration getRegisterInfo(){ ++ if(registerInfo==null){ ++ refreshRegisterInfo(); ++ } ++ return registerInfo; ++ } ++ ++ private void refreshRegisterInfo(){ ++ Registration packet = new Registration(); ++ packet.setFrom(connection.getUser()); ++ packet.setType(IQ.Type.GET); ++ packet.setTo(entityJID); ++ PacketCollector collector = ++ connection.createPacketCollector(new PacketIDFilter(packet.getPacketID())); ++ connection.sendPacket(packet); ++ Packet result = collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ collector.cancel(); ++ if(result instanceof Registration && result.getError()==null){ ++ Registration register = (Registration)result; ++ this.registerInfo = register; ++ } ++ } ++ ++ /** ++ * Checks if this gateway supports In-Band registration ++ * @return true if In-Band registration is supported ++ * @throws XMPPException ++ */ ++ public boolean canRegister() throws XMPPException{ ++ if(info==null){ ++ discoverInfo(); ++ } ++ return info.containsFeature("jabber:iq:register"); ++ } ++ ++ /** ++ * Returns all fields that are required to register to this gateway ++ * @return a list of required fields ++ */ ++ public List<String> getRequiredFields(){ ++ return getRegisterInfo().getRequiredFields(); ++ } ++ ++ /** ++ * Returns the name as proposed in this gateways identity discovered via service ++ * discovery ++ * @return a String of its name ++ * @throws XMPPException ++ */ ++ public String getName() throws XMPPException{ ++ if(identity==null){ ++ discoverInfo(); ++ } ++ return identity.getName(); ++ } ++ ++ /** ++ * Returns the type as proposed in this gateways identity discovered via service ++ * discovery. See {@link http://xmpp.org/registrar/disco-categories.html} for ++ * possible types ++ * @return a String describing the type ++ * @throws XMPPException ++ */ ++ public String getType() throws XMPPException{ ++ if(identity==null){ ++ discoverInfo(); ++ } ++ return identity.getType(); ++ } ++ ++ /** ++ * Returns true if the registration informations indicates that you are already ++ * registered with this gateway ++ * @return true if already registered ++ * @throws XMPPException ++ */ ++ public boolean isRegistered() throws XMPPException{ ++ return getRegisterInfo().isRegistered(); ++ } ++ ++ /** ++ * Returns the value of specific field of the registration information. Can be used ++ * to retrieve for example to retrieve username/password used on an already registered ++ * gateway. ++ * @param fieldName name of the field ++ * @return a String containing the value of the field or null ++ */ ++ public String getField(String fieldName){ ++ return getRegisterInfo().getField(fieldName); ++ } ++ ++ /** ++ * Returns a List of Strings of all field names which contain values. ++ * @return a List of field names ++ */ ++ public List<String> getFieldNames(){ ++ return getRegisterInfo().getFieldNames(); ++ } ++ ++ /** ++ * A convenience method for retrieving the username of an existing account ++ * @return String describing the username ++ */ ++ public String getUsername(){ ++ return getField("username"); ++ } ++ ++ /** ++ * A convenience method for retrieving the password of an existing accoung ++ * @return String describing the password ++ */ ++ public String getPassword(){ ++ return getField("password"); ++ } ++ ++ /** ++ * Returns instructions for registering with this gateway ++ * @return String containing instructions ++ */ ++ public String getInstructions(){ ++ return getRegisterInfo().getInstructions(); ++ } ++ ++ /** ++ * With this method you can register with this gateway or modify an existing registration ++ * @param username String describing the username ++ * @param password String describing the password ++ * @param fields additional fields like email. ++ * @throws XMPPException ++ */ ++ public void register(String username, String password, Map<String,String> fields)throws XMPPException{ ++ if(getRegisterInfo().isRegistered()) { ++ throw new IllegalStateException("You are already registered with this gateway"); ++ } ++ Registration register = new Registration(); ++ register.setFrom(connection.getUser()); ++ register.setTo(entityJID); ++ register.setType(IQ.Type.SET); ++ register.setUsername(username); ++ register.setPassword(password); ++ for(String s : fields.keySet()){ ++ register.addAttribute(s, fields.get(s)); ++ } ++ PacketCollector resultCollector = ++ connection.createPacketCollector(new PacketIDFilter(register.getPacketID())); ++ connection.sendPacket(register); ++ Packet result = ++ resultCollector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ resultCollector.cancel(); ++ if(result!=null && result instanceof IQ){ ++ IQ resultIQ = (IQ)result; ++ if(resultIQ.getError()!=null){ ++ throw new XMPPException(resultIQ.getError()); ++ } ++ if(resultIQ.getType()==IQ.Type.ERROR){ ++ throw new XMPPException(resultIQ.getError()); ++ } ++ connection.addPacketListener(new GatewayPresenceListener(), ++ new PacketTypeFilter(Presence.class)); ++ roster.createEntry(entityJID, getIdentity().getName(), new String[]{}); ++ } ++ else{ ++ throw new XMPPException("Packet reply timeout"); ++ } ++ } ++ ++ /** ++ * A convenience method for registering or modifying an account on this gateway without ++ * additional fields ++ * @param username String describing the username ++ * @param password String describing the password ++ * @throws XMPPException ++ */ ++ public void register(String username, String password) throws XMPPException{ ++ register(username, password,new HashMap<String,String>()); ++ } ++ ++ /** ++ * This method removes an existing registration from this gateway ++ * @throws XMPPException ++ */ ++ public void unregister() throws XMPPException{ ++ Registration register = new Registration(); ++ register.setFrom(connection.getUser()); ++ register.setTo(entityJID); ++ register.setType(IQ.Type.SET); ++ register.setRemove(true); ++ PacketCollector resultCollector = ++ connection.createPacketCollector(new PacketIDFilter(register.getPacketID())); ++ connection.sendPacket(register); ++ Packet result = resultCollector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ resultCollector.cancel(); ++ if(result!=null && result instanceof IQ){ ++ IQ resultIQ = (IQ)result; ++ if(resultIQ.getError()!=null){ ++ throw new XMPPException(resultIQ.getError()); ++ } ++ if(resultIQ.getType()==IQ.Type.ERROR){ ++ throw new XMPPException(resultIQ.getError()); ++ } ++ RosterEntry gatewayEntry = roster.getEntry(entityJID); ++ roster.removeEntry(gatewayEntry); ++ } ++ else{ ++ throw new XMPPException("Packet reply timeout"); ++ } ++ } ++ ++ /** ++ * Lets you login manually in this gateway. Normally a gateway logins you when it ++ * receives the first presence broadcasted by your server. But it is possible to ++ * manually login and logout by sending a directed presence. This method sends an ++ * empty available presence direct to the gateway. ++ */ ++ public void login(){ ++ Presence presence = new Presence(Presence.Type.available); ++ login(presence); ++ } ++ ++ /** ++ * This method lets you send the presence direct to the gateway. Type, To and From ++ * are modified. ++ * @param presence the presence used to login to gateway ++ */ ++ public void login(Presence presence){ ++ presence.setType(Presence.Type.available); ++ presence.setTo(entityJID); ++ presence.setFrom(connection.getUser()); ++ connection.sendPacket(presence); ++ } ++ ++ /** ++ * This method logs you out from this gateway by sending an unavailable presence ++ * to directly to this gateway. ++ */ ++ public void logout(){ ++ Presence presence = new Presence(Presence.Type.unavailable); ++ presence.setTo(entityJID); ++ presence.setFrom(connection.getUser()); ++ connection.sendPacket(presence); ++ } ++ ++ private class GatewayPresenceListener implements PacketListener{ ++ ++ public void processPacket(Packet packet) { ++ if(packet instanceof Presence){ ++ Presence presence = (Presence)packet; ++ if(entityJID.equals(presence.getFrom()) && ++ roster.contains(presence.getFrom()) && ++ presence.getType().equals(Presence.Type.subscribe)){ ++ Presence response = new Presence(Presence.Type.subscribed); ++ response.setTo(presence.getFrom()); ++ response.setFrom(StringUtils.parseBareAddress(connection.getUser())); ++ connection.sendPacket(response); ++ } ++ } ++ ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/GatewayManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/GatewayManager.java +new file mode 100644 +index 0000000..eee9cfc +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/GatewayManager.java +@@ -0,0 +1,199 @@ ++package org.jivesoftware.smackx; ++ ++import java.util.ArrayList; ++import java.util.HashMap; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.Roster; ++import org.jivesoftware.smack.RosterEntry; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.util.StringUtils; ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++import org.jivesoftware.smackx.packet.DiscoverItems; ++import org.jivesoftware.smackx.packet.DiscoverInfo.Identity; ++import org.jivesoftware.smackx.packet.DiscoverItems.Item; ++ ++/** ++ * This class is the general entry point to gateway interaction (XEP-0100). ++ * This class discovers available gateways on the users servers, and ++ * can give you also a list of gateways the you user is registered with which ++ * are not on his server. All actual interaction with a gateway is handled in the ++ * class {@see Gateway}. ++ * @author Till Klocke ++ * ++ */ ++public class GatewayManager { ++ ++ private static Map<Connection,GatewayManager> instances = ++ new HashMap<Connection,GatewayManager>(); ++ ++ private ServiceDiscoveryManager sdManager; ++ ++ private Map<String,Gateway> localGateways = new HashMap<String,Gateway>(); ++ ++ private Map<String,Gateway> nonLocalGateways = new HashMap<String,Gateway>(); ++ ++ private Map<String,Gateway> gateways = new HashMap<String,Gateway>(); ++ ++ private Connection connection; ++ ++ private Roster roster; ++ ++ private GatewayManager(){ ++ ++ } ++ ++ /** ++ * Creates a new instance of GatewayManager ++ * @param connection ++ * @throws XMPPException ++ */ ++ private GatewayManager(Connection connection) throws XMPPException{ ++ this.connection = connection; ++ this.roster = connection.getRoster(); ++ sdManager = ServiceDiscoveryManager.getInstanceFor(connection); ++ } ++ ++ /** ++ * Loads all gateways the users server offers ++ * @throws XMPPException ++ */ ++ private void loadLocalGateways() throws XMPPException{ ++ DiscoverItems items = sdManager.discoverItems(connection.getHost()); ++ Iterator<Item> iter = items.getItems(); ++ while(iter.hasNext()){ ++ String itemJID = iter.next().getEntityID(); ++ discoverGateway(itemJID); ++ } ++ } ++ ++ /** ++ * Discovers {@link DiscoveryInfo} and {@link DiscoveryInfo.Identity} of a gateway ++ * and creates a {@link Gateway} object representing this gateway. ++ * @param itemJID ++ * @throws XMPPException ++ */ ++ private void discoverGateway(String itemJID) throws XMPPException{ ++ DiscoverInfo info = sdManager.discoverInfo(itemJID); ++ Iterator<Identity> i = info.getIdentities(); ++ ++ while(i.hasNext()){ ++ Identity identity = i.next(); ++ String category = identity.getCategory(); ++ if(category.toLowerCase().equals("gateway")){ ++ gateways.put(itemJID, new Gateway(connection,itemJID)); ++ if(itemJID.contains(connection.getHost())){ ++ localGateways.put(itemJID, ++ new Gateway(connection,itemJID,info,identity)); ++ } ++ else{ ++ nonLocalGateways.put(itemJID, ++ new Gateway(connection,itemJID,info,identity)); ++ } ++ break; ++ } ++ } ++ } ++ ++ /** ++ * Loads all getways which are in the users roster, but are not supplied by the ++ * users server ++ * @throws XMPPException ++ */ ++ private void loadNonLocalGateways() throws XMPPException{ ++ if(roster!=null){ ++ for(RosterEntry entry : roster.getEntries()){ ++ if(entry.getUser().equalsIgnoreCase(StringUtils.parseServer(entry.getUser())) && ++ !entry.getUser().contains(connection.getHost())){ ++ discoverGateway(entry.getUser()); ++ } ++ } ++ } ++ } ++ ++ /** ++ * Returns an instance of GatewayManager for the given connection. If no instance for ++ * this connection exists a new one is created and stored in a Map. ++ * @param connection ++ * @return an instance of GatewayManager ++ * @throws XMPPException ++ */ ++ public GatewayManager getInstanceFor(Connection connection) throws XMPPException{ ++ synchronized(instances){ ++ if(instances.containsKey(connection)){ ++ return instances.get(connection); ++ } ++ GatewayManager instance = new GatewayManager(connection); ++ instances.put(connection, instance); ++ return instance; ++ } ++ } ++ ++ /** ++ * Returns a list of gateways which are offered by the users server, wether the ++ * user is registered to them or not. ++ * @return a List of Gateways ++ * @throws XMPPException ++ */ ++ public List<Gateway> getLocalGateways() throws XMPPException{ ++ if(localGateways.size()==0){ ++ loadLocalGateways(); ++ } ++ return new ArrayList<Gateway>(localGateways.values()); ++ } ++ ++ /** ++ * Returns a list of gateways the user has in his roster, but which are offered by ++ * remote servers. But note that this list isn't automatically refreshed. You have to ++ * refresh is manually if needed. ++ * @return a list of gateways ++ * @throws XMPPException ++ */ ++ public List<Gateway> getNonLocalGateways() throws XMPPException{ ++ if(nonLocalGateways.size()==0){ ++ loadNonLocalGateways(); ++ } ++ return new ArrayList<Gateway>(nonLocalGateways.values()); ++ } ++ ++ /** ++ * Refreshes the list of gateways offered by remote servers. ++ * @throws XMPPException ++ */ ++ public void refreshNonLocalGateways() throws XMPPException{ ++ loadNonLocalGateways(); ++ } ++ ++ /** ++ * Returns a Gateway object for a given JID. Please note that it is not checked if ++ * the JID belongs to valid gateway. If this JID doesn't belong to valid gateway ++ * all operations on this Gateway object should fail with a XMPPException. But there is ++ * no guarantee for that. ++ * @param entityJID ++ * @return a Gateway object ++ */ ++ public Gateway getGateway(String entityJID){ ++ if(localGateways.containsKey(entityJID)){ ++ return localGateways.get(entityJID); ++ } ++ if(nonLocalGateways.containsKey(entityJID)){ ++ return nonLocalGateways.get(entityJID); ++ } ++ if(gateways.containsKey(entityJID)){ ++ return gateways.get(entityJID); ++ } ++ Gateway gateway = new Gateway(connection,entityJID); ++ if(entityJID.contains(connection.getHost())){ ++ localGateways.put(entityJID, gateway); ++ } ++ else{ ++ nonLocalGateways.put(entityJID, gateway); ++ } ++ gateways.put(entityJID, gateway); ++ return gateway; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/GroupChatInvitation.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/GroupChatInvitation.java +new file mode 100644 +index 0000000..a9ed35e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/GroupChatInvitation.java +@@ -0,0 +1,115 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * A group chat invitation packet extension, which is used to invite other ++ * users to a group chat room. To invite a user to a group chat room, address ++ * a new message to the user and set the room name appropriately, as in the ++ * following code example: ++ * ++ * <pre> ++ * Message message = new Message("user@chat.example.com"); ++ * message.setBody("Join me for a group chat!"); ++ * message.addExtension(new GroupChatInvitation("room@chat.example.com");); ++ * con.sendPacket(message); ++ * </pre> ++ * ++ * To listen for group chat invitations, use a PacketExtensionFilter for the ++ * <tt>x</tt> element name and <tt>jabber:x:conference</tt> namespace, as in the ++ * following code example: ++ * ++ * <pre> ++ * PacketFilter filter = new PacketExtensionFilter("x", "jabber:x:conference"); ++ * // Create a packet collector or packet listeners using the filter... ++ * </pre> ++ * ++ * <b>Note</b>: this protocol is outdated now that the Multi-User Chat (MUC) JEP is available ++ * (<a href="http://www.jabber.org/jeps/jep-0045.html">JEP-45</a>). However, most ++ * existing clients still use this older protocol. Once MUC support becomes more ++ * widespread, this API may be deprecated. ++ * ++ * @author Matt Tucker ++ */ ++public class GroupChatInvitation implements PacketExtension { ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "x"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "jabber:x:conference"; ++ ++ private String roomAddress; ++ ++ /** ++ * Creates a new group chat invitation to the specified room address. ++ * GroupChat room addresses are in the form <tt>room@service</tt>, ++ * where <tt>service</tt> is the name of groupchat server, such as ++ * <tt>chat.example.com</tt>. ++ * ++ * @param roomAddress the address of the group chat room. ++ */ ++ public GroupChatInvitation(String roomAddress) { ++ this.roomAddress = roomAddress; ++ } ++ ++ /** ++ * Returns the address of the group chat room. GroupChat room addresses ++ * are in the form <tt>room@service</tt>, where <tt>service</tt> is ++ * the name of groupchat server, such as <tt>chat.example.com</tt>. ++ * ++ * @return the address of the group chat room. ++ */ ++ public String getRoomAddress() { ++ return roomAddress; ++ } ++ ++ public String getElementName() { ++ return ELEMENT_NAME; ++ } ++ ++ public String getNamespace() { ++ return NAMESPACE; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<x xmlns=\"jabber:x:conference\" jid=\"").append(roomAddress).append("\"/>"); ++ return buf.toString(); ++ } ++ ++ public static class Provider implements PacketExtensionProvider { ++ public PacketExtension parseExtension (XmlPullParser parser) throws Exception { ++ String roomAddress = parser.getAttributeValue("", "jid"); ++ // Advance to end of extension. ++ parser.next(); ++ return new GroupChatInvitation(roomAddress); ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/LLServiceDiscoveryManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/LLServiceDiscoveryManager.java +new file mode 100644 +index 0000000..07f40f2 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/LLServiceDiscoveryManager.java +@@ -0,0 +1,634 @@ ++/* ++ * Copyright 2009 Jonas Ã…dahl. ++ * ++ * All rights reserved. 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.smackx; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import java.util.concurrent.ConcurrentHashMap; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.LLPresence; ++import org.jivesoftware.smack.LLPresenceListener; ++import org.jivesoftware.smack.LLService; ++import org.jivesoftware.smack.LLServiceConnectionListener; ++import org.jivesoftware.smack.LLServiceListener; ++import org.jivesoftware.smack.LLServiceStateListener; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.XMPPLLConnection; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Message; ++import org.jivesoftware.smackx.entitycaps.CapsVerListener; ++import org.jivesoftware.smackx.entitycaps.EntityCapsManager; ++import org.jivesoftware.smackx.packet.DataForm; ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++import org.jivesoftware.smackx.packet.DiscoverItems; ++ ++/** ++ * LLServiceDiscoveryManager acts as a wrapper around ServiceDiscoveryManager ++ * as ServiceDiscoveryManager only creates an interface for requesting service ++ * information on existing connections. Simply said it creates new connections ++ * when needed, uses already active connections when appropriate and applies ++ * values to new connections. ++ * ++ * @author Jonas Ã…dahl ++ */ ++public class LLServiceDiscoveryManager implements ServiceDiscoveryManagerInterface { ++ private static Map<LLService,LLServiceDiscoveryManager> serviceManagers = ++ new ConcurrentHashMap<LLService,LLServiceDiscoveryManager>(); ++ ++ private Map<String, NodeInformationProvider> nodeInformationProviders = ++ new ConcurrentHashMap<String,NodeInformationProvider>(); ++ private final List<String> features = ++ new ArrayList<String>(); ++ private DataForm extendedInfo = null; ++ ++ private LLService service; ++ private EntityCapsManager capsManager; ++ ++ /*static { ++ XMPPLLConnection.addLLConnectionListener(new ConnectionServiceMaintainer()); ++ }*/ ++ ++ public static void addServiceListener() { ++ LLService.addLLServiceListener(new LLServiceListener() { ++ public void serviceCreated(LLService service) { ++ addLLServiceDiscoveryManager( ++ new LLServiceDiscoveryManager(service)); ++ } ++ }); ++ } ++ ++ private LLServiceDiscoveryManager(LLService llservice) { ++ this.service = llservice; ++ ++ // Add LLService state listener ++ service.addServiceStateListener(new LLServiceStateListener() { ++ private void removeEntry() { ++ removeLLServiceDiscoveryManager(service); ++ } ++ ++ public void serviceClosed() { ++ removeEntry(); ++ } ++ ++ public void serviceClosedOnError(Exception e) { ++ removeEntry(); ++ } ++ ++ public void unknownOriginMessage(Message e) { ++ // ignore ++ } ++ ++ public void serviceNameChanged(String n, String o) { ++ // Remove entries ++ capsManager.removeUserCapsNode(n); ++ capsManager.removeUserCapsNode(o); ++ LLPresence np = service.getPresenceByServiceName(n); ++ LLPresence op = service.getPresenceByServiceName(o); ++ ++ // Add existing values, if any ++ if (np != null && np.getNode() != null && np.getVer() != null) ++ capsManager.addUserCapsNode(n, np.getNode() + "#" + np.getVer()); ++ if (op != null && op.getNode() != null && op.getVer() != null) ++ capsManager.addUserCapsNode(o, op.getNode() + "#" + op.getVer()); ++ } ++ }); ++ ++ // Entity Capabilities ++ capsManager = new EntityCapsManager(this); ++ capsManager.addCapsVerListener(new CapsPresenceRenewer()); ++ capsManager.calculateEntityCapsVersion(getOwnDiscoverInfo(), ++ ServiceDiscoveryManager.getIdentityType(), ++ ServiceDiscoveryManager.getIdentityName(), ++ extendedInfo); ++ ++ // Add presence listener. The presence listener will gather ++ // entity caps data ++ service.addPresenceListener(new LLPresenceListener() { ++ public void presenceNew(LLPresence presence) { ++ if (presence.getHash() != null && ++ presence.getNode() != null && ++ presence.getVer() != null) { ++ // Add presence to caps manager ++ capsManager.addUserCapsNode(presence.getServiceName(), ++ presence.getNode() + "#" + presence.getVer()); ++ } ++ } ++ ++ public void presenceRemove(LLPresence presence) { ++ ++ } ++ }); ++ ++ service.addLLServiceConnectionListener(new ConnectionServiceMaintainer()); ++ } ++ ++ /** ++ * Add LLServiceDiscoveryManager to the map of existing ones. ++ */ ++ private static void addLLServiceDiscoveryManager(LLServiceDiscoveryManager manager) { ++ serviceManagers.put(manager.service, manager); ++ } ++ ++ /** ++ * Remove LLServiceDiscoveryManager from the map of existing ones. ++ */ ++ private static void removeLLServiceDiscoveryManager(LLService service) { ++ serviceManagers.remove(service); ++ } ++ ++ /** ++ * Get the LLServiceDiscoveryManager instance for a specific Link-local service. ++ * ++ * @param service ++ */ ++ public static LLServiceDiscoveryManager getInstanceFor(LLService service) { ++ return serviceManagers.get(service); ++ } ++ ++ /** ++ * Returns the name of the client that will be returned when asked for the client identity ++ * in a disco request. The name could be any value you need to identity this client. ++ * ++ * @return the name of the client that will be returned when asked for the client identity ++ * in a disco request. ++ */ ++ public static String getIdentityName() { ++ return ServiceDiscoveryManager.getIdentityName(); ++ } ++ ++ /** ++ * Sets the name of the client that will be returned when asked for the client identity ++ * in a disco request. The name could be any value you need to identity this client. ++ * ++ * @param name the name of the client that will be returned when asked for the client identity ++ * in a disco request. ++ */ ++ public static void setIdentityName(String name) { ++ ServiceDiscoveryManager.setIdentityType(name); ++ } ++ ++ /** ++ * Returns the type of client that will be returned when asked for the client identity in a ++ * disco request. The valid types are defined by the category client. Follow this link to learn ++ * the possible types: <a href="http://www.jabber.org/registrar/disco-categories.html#client">Jabber::Registrar</a>. ++ * ++ * @return the type of client that will be returned when asked for the client identity in a ++ * disco request. ++ */ ++ public static String getIdentityType() { ++ return ServiceDiscoveryManager.getIdentityType(); ++ } ++ ++ /** ++ * Sets the type of client that will be returned when asked for the client identity in a ++ * disco request. The valid types are defined by the category client. Follow this link to learn ++ * the possible types: <a href="http://www.jabber.org/registrar/disco-categories.html#client">Jabber::Registrar</a>. ++ * ++ * @param type the type of client that will be returned when asked for the client identity in a ++ * disco request. ++ */ ++ public static void setIdentityType(String type) { ++ ServiceDiscoveryManager.setIdentityType(type); ++ } ++ ++ /** ++ * Add discover info response data. ++ * ++ * @param response the discover info response packet ++ */ ++ public void addDiscoverInfoTo(DiscoverInfo response) { ++ // Set this client identity ++ DiscoverInfo.Identity identity = new DiscoverInfo.Identity("client", ++ getIdentityName()); ++ identity.setType(getIdentityType()); ++ response.addIdentity(identity); ++ // Add the registered features to the response ++ synchronized (features) { ++ // Add Entity Capabilities (XEP-0115) feature node. ++ response.addFeature("http://jabber.org/protocol/caps"); ++ ++ for (Iterator<String> it = getFeatures(); it.hasNext();) { ++ response.addFeature(it.next()); ++ } ++ if (extendedInfo != null) { ++ response.addExtension(extendedInfo); ++ } ++ } ++ } ++ ++ /** ++ * Get a DiscoverInfo for the current entity caps node. ++ * ++ * @return a DiscoverInfo for the current entity caps node ++ */ ++ public DiscoverInfo getOwnDiscoverInfo() { ++ DiscoverInfo di = new DiscoverInfo(); ++ di.setType(IQ.Type.RESULT); ++ di.setNode(capsManager.getNode() + "#" + getEntityCapsVersion()); ++ ++ // Add discover info ++ addDiscoverInfoTo(di); ++ ++ for (String feature : features) { ++ di.addFeature(feature); ++ } ++ ++ return di; ++ } ++ ++ /** ++ * Returns a new or already established connection to the given service name. ++ * ++ * @param serviceName remote service to which we wish to be connected to. ++ * @returns an established connection to the given service name. ++ */ ++ private XMPPLLConnection getConnection(String serviceName) throws XMPPException { ++ return service.getConnection(serviceName); ++ } ++ ++ /** ++ * Returns a ServiceDiscoveryManager instance for a new or already established ++ * connection to the given service name. ++ * ++ * @param serviceName the name of the service we wish to get the ServiceDiscoveryManager instance for. ++ * @returns the ServiceDiscoveryManager instance. ++ */ ++ private ServiceDiscoveryManager getInstance(String serviceName) throws XMPPException { ++ return ServiceDiscoveryManager.getInstanceFor(getConnection(serviceName)); ++ } ++ ++ /** ++ * Registers extended discovery information of this XMPP entity. When this ++ * client is queried for its information this data form will be returned as ++ * specified by XEP-0128. ++ * <p> ++ * ++ * Since no packet is actually sent to the server it is safe to perform this ++ * operation before logging to the server. In fact, you may want to ++ * configure the extended info before logging to the server so that the ++ * information is already available if it is required upon login. ++ * ++ * @param info ++ * the data form that contains the extend service discovery ++ * information. ++ */ ++ public void setExtendedInfo(DataForm info) { ++ extendedInfo = info; ++ ++ // set for already active connections ++ for (XMPPLLConnection connection : service.getConnections()) ++ ServiceDiscoveryManager.getInstanceFor(connection).setExtendedInfo(info); ++ ++ renewEntityCapsVersion(); ++ } ++ ++ /** ++ * Removes the dataform containing extended service discovery information ++ * from the information returned by this XMPP entity.<p> ++ * ++ * Since no packet is actually sent to the server it is safe to perform this ++ * operation before logging to the server. ++ */ ++ public void removeExtendedInfo() { ++ extendedInfo = null; ++ ++ // remove for already active connections ++ for (XMPPLLConnection connection : service.getConnections()) ++ ServiceDiscoveryManager.getInstanceFor(connection).removeExtendedInfo(); ++ ++ renewEntityCapsVersion(); ++ } ++ ++ /** ++ * Returns the discovered information of a given XMPP entity addressed by its JID. ++ * ++ * @param entityID the address of the XMPP entity. ++ * @return the discovered information. ++ * @throws XMPPException if the operation failed for some reason. ++ */ ++ public DiscoverInfo discoverInfo(String serviceName) throws XMPPException { ++ DiscoverInfo info = capsManager.getDiscoverInfoByUser(serviceName); ++ ++ // If there is no cached information retrieve new one ++ if (info == null) { ++ // If the caps node is known, use it in the request. ++ String node = null; ++ ++ if (capsManager != null) { ++ // Get the newest node#version ++ node = capsManager.getNodeVersionByUser(serviceName); ++ } ++ ++ return discoverInfo(serviceName, node); ++ } ++ else { ++ return info; ++ } ++ } ++ ++ /** ++ * Returns the discovered information of a given XMPP entity addressed by its JID and ++ * note attribute. Use this message only when trying to query information which is not ++ * directly addressable. ++ * ++ * @param entityID the address of the XMPP entity. ++ * @param node the attribute that supplements the 'jid' attribute. ++ * @return the discovered information. ++ * @throws XMPPException if the operation failed for some reason. ++ */ ++ public DiscoverInfo discoverInfo(String serviceName, String node) throws XMPPException { ++ // Discover the entity's info ++ DiscoverInfo disco = new DiscoverInfo(); ++ disco.setType(IQ.Type.GET); ++ disco.setTo(serviceName); ++ disco.setNode(node); ++ ++ IQ result = service.getIQResponse(disco); ++ if (result == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ if (result.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(result.getError()); ++ } ++ if (result instanceof DiscoverInfo) { ++ return (DiscoverInfo) result; ++ } ++ throw new XMPPException("Result was not a disco info reply."); ++ } ++ ++ /** ++ * Returns the discovered items of a given XMPP entity addressed by its JID. ++ * ++ * @param entityID the address of the XMPP entity. ++ * @return the discovered information. ++ * @throws XMPPException if the operation failed for some reason. ++ */ ++ public DiscoverItems discoverItems(String entityID) throws XMPPException { ++ return discoverItems(entityID, null); ++ } ++ ++ /** ++ * Returns the discovered items of a given XMPP entity addressed by its JID and ++ * note attribute. Use this message only when trying to query information which is not ++ * directly addressable. ++ * ++ * @param entityID the address of the XMPP entity. ++ * @param node the attribute that supplements the 'jid' attribute. ++ * @return the discovered items. ++ * @throws XMPPException if the operation failed for some reason. ++ */ ++ public DiscoverItems discoverItems(String serviceName, String node) throws XMPPException { ++ // Discover the entity's items ++ DiscoverItems disco = new DiscoverItems(); ++ disco.setType(IQ.Type.GET); ++ disco.setTo(serviceName); ++ disco.setNode(node); ++ ++ IQ result = service.getIQResponse(disco); ++ if (result == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ if (result.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(result.getError()); ++ } ++ if (result instanceof DiscoverItems) { ++ return (DiscoverItems) result; ++ } ++ throw new XMPPException("Result was not a disco items reply."); ++ } ++ ++ /** ++ * Sets the NodeInformationProvider responsible for providing information ++ * (ie items) related to a given node. Every time this client receives a disco request ++ * regarding the items of a given node, the provider associated to that node will be the ++ * responsible for providing the requested information.<p> ++ * ++ * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the ++ * NodeInformationProvider will provide information about the rooms where the user has joined. ++ * ++ * @param node the node whose items will be provided by the NodeInformationProvider. ++ * @param listener the NodeInformationProvider responsible for providing items related ++ * to the node. ++ */ ++ public void setNodeInformationProvider(String node, ++ NodeInformationProvider listener) { ++ // store this NodeInformationProvider so we can add it to new connections. ++ nodeInformationProviders.put(node, listener); ++ ++ // set for already active connections ++ for (XMPPLLConnection connection : service.getConnections()) ++ ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider(node, listener); ++ } ++ ++ /** ++ * Removes the NodeInformationProvider responsible for providing information ++ * (ie items) related to a given node. This means that no more information will be ++ * available for the specified node. ++ * ++ * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the ++ * NodeInformationProvider will provide information about the rooms where the user has joined. ++ * ++ * @param node the node to remove the associated NodeInformationProvider. ++ */ ++ public void removeNodeInformationProvider(String node) { ++ // remove from wrapper class ++ nodeInformationProviders.remove(node); ++ ++ // remove from existing connections ++ for (XMPPLLConnection connection : service.getConnections()) ++ ServiceDiscoveryManager.getInstanceFor(connection).removeNodeInformationProvider(node); ++ } ++ ++ /** ++ * Returns the supported features by this XMPP entity. ++ * ++ * @return an Iterator on the supported features by this XMPP entity. ++ */ ++ public Iterator<String> getFeatures() { ++ synchronized (features) { ++ return Collections.unmodifiableList(new ArrayList<String>(features)).iterator(); ++ } ++ } ++ ++ /** ++ * Registers that a new feature is supported by this XMPP entity. When this client is ++ * queried for its information the registered features will be answered.<p> ++ * ++ * Since no packet is actually sent to the server it is safe to perform this operation ++ * before logging to the server. In fact, you may want to configure the supported features ++ * before logging to the server so that the information is already available if it is required ++ * upon login. ++ * ++ * @param feature the feature to register as supported. ++ */ ++ public void addFeature(String feature) { ++ synchronized (features) { ++ features.add(feature); ++ } ++ ++ renewEntityCapsVersion(); ++ } ++ ++ /** ++ * Removes the specified feature from the supported features by this XMPP entity.<p> ++ * ++ * Since no packet is actually sent to the server it is safe to perform this operation ++ * before logging to the server. ++ * ++ * @param feature the feature to remove from the supported features. ++ */ ++ public void removeFeature(String feature) { ++ synchronized (features) { ++ features.remove(feature); ++ for (XMPPLLConnection connection : service.getConnections()) ++ ServiceDiscoveryManager.getInstanceFor(connection).removeFeature(feature); ++ } ++ ++ renewEntityCapsVersion(); ++ } ++ ++ /** ++ * Returns true if the specified feature is registered in the ServiceDiscoveryManager. ++ * ++ * @param feature the feature to look for. ++ * @return a boolean indicating if the specified featured is registered or not. ++ */ ++ public boolean includesFeature(String feature) { ++ synchronized (features) { ++ return features.contains(feature); ++ } ++ } ++ ++ /** ++ * Returns true if the server supports publishing of items. A client may wish to publish items ++ * to the server so that the server can provide items associated to the client. These items will ++ * be returned by the server whenever the server receives a disco request targeted to the bare ++ * address of the client (i.e. user@host.com). ++ * ++ * @param entityID the address of the XMPP entity. ++ * @return true if the server supports publishing of items. ++ * @throws XMPPException if the operation failed for some reason. ++ */ ++ public boolean canPublishItems(String entityID) throws XMPPException { ++ DiscoverInfo info = discoverInfo(entityID); ++ return ServiceDiscoveryManager.canPublishItems(info); ++ } ++ ++ /** ++ * Publishes new items to a parent entity. The item elements to publish MUST have at least ++ * a 'jid' attribute specifying the Entity ID of the item, and an action attribute which ++ * specifies the action being taken for that item. Possible action values are: "update" and ++ * "remove". ++ * ++ * @param entityID the address of the XMPP entity. ++ * @param discoverItems the DiscoveryItems to publish. ++ * @throws XMPPException if the operation failed for some reason. ++ */ ++ public void publishItems(String entityID, DiscoverItems discoverItems) ++ throws XMPPException { ++ publishItems(entityID, null, discoverItems); ++ } ++ ++ /** ++ * Publishes new items to a parent entity and node. The item elements to publish MUST have at ++ * least a 'jid' attribute specifying the Entity ID of the item, and an action attribute which ++ * specifies the action being taken for that item. Possible action values are: "update" and ++ * "remove". ++ * ++ * @param entityID the address of the XMPP entity. ++ * @param node the attribute that supplements the 'jid' attribute. ++ * @param discoverItems the DiscoveryItems to publish. ++ * @throws XMPPException if the operation failed for some reason. ++ */ ++ public void publishItems(String entityID, String node, DiscoverItems discoverItems) ++ throws XMPPException { ++ getInstance(entityID).publishItems(entityID, node, discoverItems); ++ } ++ ++ private void renewEntityCapsVersion() { ++ if (capsManager != null) { ++ capsManager.calculateEntityCapsVersion(getOwnDiscoverInfo(), ++ ServiceDiscoveryManager.getIdentityType(), ++ ServiceDiscoveryManager.getIdentityName(), ++ extendedInfo); ++ } ++ } ++ ++ private String getEntityCapsVersion() { ++ if (capsManager != null) { ++ return capsManager.getCapsVersion(); ++ } ++ else { ++ return null; ++ } ++ } ++ ++ ++ /** ++ * In case that a connection is unavailable we create a new connection ++ * and push the service discovery procedure until the new connection is ++ * established. ++ */ ++ private class ConnectionServiceMaintainer implements LLServiceConnectionListener { ++ ++ public void connectionCreated(XMPPLLConnection connection) { ++ // Add service discovery for Link-local connections.\ ++ ServiceDiscoveryManager manager = ++ new ServiceDiscoveryManager(connection); ++ ++ // Set Entity Capabilities Manager ++ manager.setEntityCapsManager(capsManager); ++ ++ // Set extended info ++ manager.setExtendedInfo(extendedInfo); ++ ++ // Set node information providers ++ for (Map.Entry<String,NodeInformationProvider> entry : ++ nodeInformationProviders.entrySet()) { ++ manager.setNodeInformationProvider(entry.getKey(), entry.getValue()); ++ } ++ ++ // add features ++ synchronized (features) { ++ for (String feature : features) { ++ manager.addFeature(feature); ++ } ++ } ++ } ++ } ++ ++ private class CapsPresenceRenewer implements CapsVerListener { ++ public void capsVerUpdated(String ver) { ++ synchronized (service) { ++ try { ++ LLPresence presence = service.getLocalPresence(); ++ presence.setHash(EntityCapsManager.HASH_METHOD); ++ presence.setNode(capsManager.getNode()); ++ presence.setVer(ver); ++ service.updatePresence(presence); ++ } ++ catch (XMPPException xe) { ++ // ignore ++ } ++ } ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/LastActivityManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/LastActivityManager.java +new file mode 100644 +index 0000000..9e231c7 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/LastActivityManager.java +@@ -0,0 +1,172 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import org.jivesoftware.smack.*; ++import org.jivesoftware.smack.filter.AndFilter; ++import org.jivesoftware.smack.filter.IQTypeFilter; ++import org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smackx.packet.LastActivity; ++ ++/** ++ * A last activity manager for handling information about the last activity associated ++ * with a Jabber ID. A manager handles incoming LastActivity requests of existing ++ * Connections. It also allows to request last activity information of other users.<p> ++ * ++ * LastActivity (JEP-012) based on the sending JID's type allows for retrieval of: ++ * <ol> ++ * <li>How long a particular user has been idle ++ * <li>How long a particular user has been logged-out and the message the specified when doing so. ++ * <li>How long a host has been up. ++ * </ol> ++ * <p/> ++ * ++ * For example to get the idle time of a user logged in a resource, simple send the ++ * LastActivity packet to them, as in the following code:<p> ++ * ++ * <pre> ++ * Connection con = new XMPPConnection("jabber.org"); ++ * con.login("john", "doe"); ++ * LastActivity activity = LastActivity.getLastActivity(con, "xray@jabber.org/Smack"); ++ * </pre> ++ * ++ * To get the lapsed time since the last user logout is the same as above but with ++ * out the resource: ++ * <pre> ++ * LastActivity activity = LastActivity.getLastActivity(con, "xray@jabber.org"); ++ * </pre> ++ * ++ * To get the uptime of a host, you simple send the LastActivity packet to it, as in the ++ * following code example:<p> ++ * ++ * <pre> ++ * LastActivity activity = LastActivity.getLastActivity(con, "jabber.org"); ++ * </pre> ++ * ++ * @author Gabriel Guardincerri ++ */ ++ ++public class LastActivityManager { ++ ++ private long lastMessageSent; ++ ++ private Connection connection; ++ ++ // Enable the LastActivity support on every established connection ++ static { ++ Connection.addConnectionCreationListener(new ConnectionCreationListener() { ++ public void connectionCreated(Connection connection) { ++ new LastActivityManager(connection); ++ } ++ }); ++ } ++ ++ /** ++ * Creates a last activity manager to response last activity requests. ++ * ++ * @param connection The Connection that the last activity requests will use. ++ */ ++ private LastActivityManager(Connection connection) { ++ this.connection = connection; ++ ++ // Listen to all the sent messages to reset the idle time on each one ++ connection.addPacketSendingListener(new PacketListener() { ++ public void processPacket(Packet packet) { ++ resetIdleTime(); ++ } ++ }, null); ++ ++ // Register a listener for a last activity query ++ connection.addPacketListener(new PacketListener() { ++ ++ public void processPacket(Packet packet) { ++ LastActivity message = new LastActivity(); ++ message.setType(IQ.Type.RESULT); ++ message.setTo(packet.getFrom()); ++ message.setFrom(packet.getTo()); ++ message.setPacketID(packet.getPacketID()); ++ message.setLastActivity(getIdleTime()); ++ ++ LastActivityManager.this.connection.sendPacket(message); ++ } ++ ++ }, new AndFilter(new IQTypeFilter(IQ.Type.GET), new PacketTypeFilter(LastActivity.class))); ++ } ++ ++ /** ++ * Resets the idle time to 0, this should be invoked when a new message is ++ * sent. ++ */ ++ private void resetIdleTime() { ++ lastMessageSent = System.currentTimeMillis(); ++ } ++ ++ /** ++ * The idle time is the lapsed time between the last message sent and now. ++ * ++ * @return the lapsed time between the last message sent and now. ++ */ ++ private long getIdleTime() { ++ long now = System.currentTimeMillis(); ++ return ((now - lastMessageSent) / 1000); ++ } ++ ++ /** ++ * Returns the last activity of a particular jid. If the jid is a full JID ++ * (i.e., a JID of the form of 'user@host/resource') then the last activity ++ * is the idle time of that connected resource. On the other hand, when the ++ * jid is a bare JID (e.g. 'user@host') then the last activity is the lapsed ++ * time since the last logout or 0 if the user is currently logged in. Moreover, ++ * when the jid is a server or component (e.g., a JID of the form 'host') the ++ * last activity is the uptime. ++ * ++ * @param con the current Connection. ++ * @param jid the JID of the user. ++ * @return the LastActivity packet of the jid. ++ * @throws XMPPException thrown if a server error has occured. ++ */ ++ public static LastActivity getLastActivity(Connection con, String jid) ++ throws XMPPException { ++ LastActivity activity = new LastActivity(); ++ activity.setTo(jid); ++ ++ PacketCollector collector = ++ con.createPacketCollector(new PacketIDFilter(activity.getPacketID())); ++ con.sendPacket(activity); ++ ++ LastActivity response = ++ (LastActivity) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/MessageEventManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MessageEventManager.java +new file mode 100644 +index 0000000..f8f553a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MessageEventManager.java +@@ -0,0 +1,310 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import java.lang.reflect.InvocationTargetException; ++import java.lang.reflect.Method; ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++ ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.filter.PacketExtensionFilter; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.packet.Message; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smackx.packet.MessageEvent; ++ ++/** ++ * Manages message events requests and notifications. A MessageEventManager provides a high ++ * level access to request for notifications and send event notifications. It also provides ++ * an easy way to hook up custom logic when requests or notifications are received. ++ * ++ * @author Gaston Dombiak ++ */ ++public class MessageEventManager { ++ ++ private List<MessageEventNotificationListener> messageEventNotificationListeners = new ArrayList<MessageEventNotificationListener>(); ++ private List<MessageEventRequestListener> messageEventRequestListeners = new ArrayList<MessageEventRequestListener>(); ++ ++ private Connection con; ++ ++ private PacketFilter packetFilter = new PacketExtensionFilter("x", "jabber:x:event"); ++ private PacketListener packetListener; ++ ++ /** ++ * Creates a new message event manager. ++ * ++ * @param con a Connection to a XMPP server. ++ */ ++ public MessageEventManager(Connection con) { ++ this.con = con; ++ init(); ++ } ++ ++ /** ++ * Adds event notification requests to a message. For each event type that ++ * the user wishes event notifications from the message recepient for, <tt>true</tt> ++ * should be passed in to this method. ++ * ++ * @param message the message to add the requested notifications. ++ * @param offline specifies if the offline event is requested. ++ * @param delivered specifies if the delivered event is requested. ++ * @param displayed specifies if the displayed event is requested. ++ * @param composing specifies if the composing event is requested. ++ */ ++ public static void addNotificationsRequests(Message message, boolean offline, ++ boolean delivered, boolean displayed, boolean composing) ++ { ++ // Create a MessageEvent Package and add it to the message ++ MessageEvent messageEvent = new MessageEvent(); ++ messageEvent.setOffline(offline); ++ messageEvent.setDelivered(delivered); ++ messageEvent.setDisplayed(displayed); ++ messageEvent.setComposing(composing); ++ message.addExtension(messageEvent); ++ } ++ ++ /** ++ * Adds a message event request listener. The listener will be fired anytime a request for ++ * event notification is received. ++ * ++ * @param messageEventRequestListener a message event request listener. ++ */ ++ public void addMessageEventRequestListener(MessageEventRequestListener messageEventRequestListener) { ++ synchronized (messageEventRequestListeners) { ++ if (!messageEventRequestListeners.contains(messageEventRequestListener)) { ++ messageEventRequestListeners.add(messageEventRequestListener); ++ } ++ } ++ } ++ ++ /** ++ * Removes a message event request listener. The listener will be fired anytime a request for ++ * event notification is received. ++ * ++ * @param messageEventRequestListener a message event request listener. ++ */ ++ public void removeMessageEventRequestListener(MessageEventRequestListener messageEventRequestListener) { ++ synchronized (messageEventRequestListeners) { ++ messageEventRequestListeners.remove(messageEventRequestListener); ++ } ++ } ++ ++ /** ++ * Adds a message event notification listener. The listener will be fired anytime a notification ++ * event is received. ++ * ++ * @param messageEventNotificationListener a message event notification listener. ++ */ ++ public void addMessageEventNotificationListener(MessageEventNotificationListener messageEventNotificationListener) { ++ synchronized (messageEventNotificationListeners) { ++ if (!messageEventNotificationListeners.contains(messageEventNotificationListener)) { ++ messageEventNotificationListeners.add(messageEventNotificationListener); ++ } ++ } ++ } ++ ++ /** ++ * Removes a message event notification listener. The listener will be fired anytime a notification ++ * event is received. ++ * ++ * @param messageEventNotificationListener a message event notification listener. ++ */ ++ public void removeMessageEventNotificationListener(MessageEventNotificationListener messageEventNotificationListener) { ++ synchronized (messageEventNotificationListeners) { ++ messageEventNotificationListeners.remove(messageEventNotificationListener); ++ } ++ } ++ ++ /** ++ * Fires message event request listeners. ++ */ ++ private void fireMessageEventRequestListeners( ++ String from, ++ String packetID, ++ String methodName) { ++ MessageEventRequestListener[] listeners = null; ++ Method method; ++ synchronized (messageEventRequestListeners) { ++ listeners = new MessageEventRequestListener[messageEventRequestListeners.size()]; ++ messageEventRequestListeners.toArray(listeners); ++ } ++ try { ++ method = ++ MessageEventRequestListener.class.getDeclaredMethod( ++ methodName, ++ new Class[] { String.class, String.class, MessageEventManager.class }); ++ for (int i = 0; i < listeners.length; i++) { ++ method.invoke(listeners[i], new Object[] { from, packetID, this }); ++ } ++ } catch (NoSuchMethodException e) { ++ e.printStackTrace(); ++ } catch (InvocationTargetException e) { ++ e.printStackTrace(); ++ } catch (IllegalAccessException e) { ++ e.printStackTrace(); ++ } ++ } ++ ++ /** ++ * Fires message event notification listeners. ++ */ ++ private void fireMessageEventNotificationListeners( ++ String from, ++ String packetID, ++ String methodName) { ++ MessageEventNotificationListener[] listeners = null; ++ Method method; ++ synchronized (messageEventNotificationListeners) { ++ listeners = ++ new MessageEventNotificationListener[messageEventNotificationListeners.size()]; ++ messageEventNotificationListeners.toArray(listeners); ++ } ++ try { ++ method = ++ MessageEventNotificationListener.class.getDeclaredMethod( ++ methodName, ++ new Class[] { String.class, String.class }); ++ for (int i = 0; i < listeners.length; i++) { ++ method.invoke(listeners[i], new Object[] { from, packetID }); ++ } ++ } catch (NoSuchMethodException e) { ++ e.printStackTrace(); ++ } catch (InvocationTargetException e) { ++ e.printStackTrace(); ++ } catch (IllegalAccessException e) { ++ e.printStackTrace(); ++ } ++ } ++ ++ private void init() { ++ // Listens for all message event packets and fire the proper message event listeners. ++ packetListener = new PacketListener() { ++ public void processPacket(Packet packet) { ++ Message message = (Message) packet; ++ MessageEvent messageEvent = ++ (MessageEvent) message.getExtension("x", "jabber:x:event"); ++ if (messageEvent.isMessageEventRequest()) { ++ // Fire event for requests of message events ++ for (Iterator it = messageEvent.getEventTypes(); it.hasNext();) ++ fireMessageEventRequestListeners( ++ message.getFrom(), ++ message.getPacketID(), ++ ((String) it.next()).concat("NotificationRequested")); ++ } else ++ // Fire event for notifications of message events ++ for (Iterator it = messageEvent.getEventTypes(); it.hasNext();) ++ fireMessageEventNotificationListeners( ++ message.getFrom(), ++ messageEvent.getPacketID(), ++ ((String) it.next()).concat("Notification")); ++ ++ }; ++ ++ }; ++ con.addPacketListener(packetListener, packetFilter); ++ } ++ ++ /** ++ * Sends the notification that the message was delivered to the sender of the original message ++ * ++ * @param to the recipient of the notification. ++ * @param packetID the id of the message to send. ++ */ ++ public void sendDeliveredNotification(String to, String packetID) { ++ // Create the message to send ++ Message msg = new Message(to); ++ // Create a MessageEvent Package and add it to the message ++ MessageEvent messageEvent = new MessageEvent(); ++ messageEvent.setDelivered(true); ++ messageEvent.setPacketID(packetID); ++ msg.addExtension(messageEvent); ++ // Send the packet ++ con.sendPacket(msg); ++ } ++ ++ /** ++ * Sends the notification that the message was displayed to the sender of the original message ++ * ++ * @param to the recipient of the notification. ++ * @param packetID the id of the message to send. ++ */ ++ public void sendDisplayedNotification(String to, String packetID) { ++ // Create the message to send ++ Message msg = new Message(to); ++ // Create a MessageEvent Package and add it to the message ++ MessageEvent messageEvent = new MessageEvent(); ++ messageEvent.setDisplayed(true); ++ messageEvent.setPacketID(packetID); ++ msg.addExtension(messageEvent); ++ // Send the packet ++ con.sendPacket(msg); ++ } ++ ++ /** ++ * Sends the notification that the receiver of the message is composing a reply ++ * ++ * @param to the recipient of the notification. ++ * @param packetID the id of the message to send. ++ */ ++ public void sendComposingNotification(String to, String packetID) { ++ // Create the message to send ++ Message msg = new Message(to); ++ // Create a MessageEvent Package and add it to the message ++ MessageEvent messageEvent = new MessageEvent(); ++ messageEvent.setComposing(true); ++ messageEvent.setPacketID(packetID); ++ msg.addExtension(messageEvent); ++ // Send the packet ++ con.sendPacket(msg); ++ } ++ ++ /** ++ * Sends the notification that the receiver of the message has cancelled composing a reply. ++ * ++ * @param to the recipient of the notification. ++ * @param packetID the id of the message to send. ++ */ ++ public void sendCancelledNotification(String to, String packetID) { ++ // Create the message to send ++ Message msg = new Message(to); ++ // Create a MessageEvent Package and add it to the message ++ MessageEvent messageEvent = new MessageEvent(); ++ messageEvent.setCancelled(true); ++ messageEvent.setPacketID(packetID); ++ msg.addExtension(messageEvent); ++ // Send the packet ++ con.sendPacket(msg); ++ } ++ ++ public void destroy() { ++ if (con != null) { ++ con.removePacketListener(packetListener); ++ } ++ } ++ ++ protected void finalize() throws Throwable { ++ destroy(); ++ super.finalize(); ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/MessageEventNotificationListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MessageEventNotificationListener.java +new file mode 100644 +index 0000000..335dae2 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MessageEventNotificationListener.java +@@ -0,0 +1,74 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++/** ++ * ++ * A listener that is fired anytime a message event notification is received. ++ * Message event notifications are received as a consequence of the request ++ * to receive notifications when sending a message. ++ * ++ * @author Gaston Dombiak ++ */ ++public interface MessageEventNotificationListener { ++ ++ /** ++ * Called when a notification of message delivered is received. ++ * ++ * @param from the user that sent the notification. ++ * @param packetID the id of the message that was sent. ++ */ ++ public void deliveredNotification(String from, String packetID); ++ ++ /** ++ * Called when a notification of message displayed is received. ++ * ++ * @param from the user that sent the notification. ++ * @param packetID the id of the message that was sent. ++ */ ++ public void displayedNotification(String from, String packetID); ++ ++ /** ++ * Called when a notification that the receiver of the message is composing a reply is ++ * received. ++ * ++ * @param from the user that sent the notification. ++ * @param packetID the id of the message that was sent. ++ */ ++ public void composingNotification(String from, String packetID); ++ ++ /** ++ * Called when a notification that the receiver of the message is offline is received. ++ * ++ * @param from the user that sent the notification. ++ * @param packetID the id of the message that was sent. ++ */ ++ public void offlineNotification(String from, String packetID); ++ ++ /** ++ * Called when a notification that the receiver of the message cancelled the reply ++ * is received. ++ * ++ * @param from the user that sent the notification. ++ * @param packetID the id of the message that was sent. ++ */ ++ public void cancelledNotification(String from, String packetID); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/MessageEventRequestListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MessageEventRequestListener.java +new file mode 100644 +index 0000000..86e0808 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MessageEventRequestListener.java +@@ -0,0 +1,86 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++/** ++ * ++ * A listener that is fired anytime a message event request is received. ++ * Message event requests are received when the received message includes an extension ++ * like this: ++ * ++ * <pre> ++ * <x xmlns='jabber:x:event'> ++ * <offline/> ++ * <delivered/> ++ * <composing/> ++ * </x> ++ * </pre> ++ * ++ * In this example you can see that the sender of the message requests to be notified ++ * when the user couldn't receive the message because he/she is offline, the message ++ * was delivered or when the receiver of the message is composing a reply. ++ * ++ * @author Gaston Dombiak ++ */ ++public interface MessageEventRequestListener { ++ ++ /** ++ * Called when a request for message delivered notification is received. ++ * ++ * @param from the user that sent the notification. ++ * @param packetID the id of the message that was sent. ++ * @param messageEventManager the messageEventManager that fired the listener. ++ */ ++ public void deliveredNotificationRequested(String from, String packetID, ++ MessageEventManager messageEventManager); ++ ++ /** ++ * Called when a request for message displayed notification is received. ++ * ++ * @param from the user that sent the notification. ++ * @param packetID the id of the message that was sent. ++ * @param messageEventManager the messageEventManager that fired the listener. ++ */ ++ public void displayedNotificationRequested(String from, String packetID, ++ MessageEventManager messageEventManager); ++ ++ /** ++ * Called when a request that the receiver of the message is composing a reply notification is ++ * received. ++ * ++ * @param from the user that sent the notification. ++ * @param packetID the id of the message that was sent. ++ * @param messageEventManager the messageEventManager that fired the listener. ++ */ ++ public void composingNotificationRequested(String from, String packetID, ++ MessageEventManager messageEventManager); ++ ++ /** ++ * Called when a request that the receiver of the message is offline is received. ++ * ++ * @param from the user that sent the notification. ++ * @param packetID the id of the message that was sent. ++ * @param messageEventManager the messageEventManager that fired the listener. ++ */ ++ public void offlineNotificationRequested(String from, String packetID, ++ MessageEventManager messageEventManager); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/MultipleRecipientInfo.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MultipleRecipientInfo.java +new file mode 100644 +index 0000000..a9ac0f8 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MultipleRecipientInfo.java +@@ -0,0 +1,98 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import org.jivesoftware.smackx.packet.MultipleAddresses; ++ ++import java.util.List; ++ ++/** ++ * MultipleRecipientInfo keeps information about the multiple recipients extension included ++ * in a received packet. Among the information we can find the list of TO and CC addresses. ++ * ++ * @author Gaston Dombiak ++ */ ++public class MultipleRecipientInfo { ++ ++ MultipleAddresses extension; ++ ++ MultipleRecipientInfo(MultipleAddresses extension) { ++ this.extension = extension; ++ } ++ ++ /** ++ * Returns the list of {@link org.jivesoftware.smackx.packet.MultipleAddresses.Address} ++ * that were the primary recipients of the packet. ++ * ++ * @return list of primary recipients of the packet. ++ */ ++ public List getTOAddresses() { ++ return extension.getAddressesOfType(MultipleAddresses.TO); ++ } ++ ++ /** ++ * Returns the list of {@link org.jivesoftware.smackx.packet.MultipleAddresses.Address} ++ * that were the secondary recipients of the packet. ++ * ++ * @return list of secondary recipients of the packet. ++ */ ++ public List getCCAddresses() { ++ return extension.getAddressesOfType(MultipleAddresses.CC); ++ } ++ ++ /** ++ * Returns the JID of a MUC room to which responses should be sent or <tt>null</tt> if ++ * no specific address was provided. When no specific address was provided then the reply ++ * can be sent to any or all recipients. Otherwise, the user should join the specified room ++ * and send the reply to the room. ++ * ++ * @return the JID of a MUC room to which responses should be sent or <tt>null</tt> if ++ * no specific address was provided. ++ */ ++ public String getReplyRoom() { ++ List replyRoom = extension.getAddressesOfType(MultipleAddresses.REPLY_ROOM); ++ return replyRoom.isEmpty() ? null : ((MultipleAddresses.Address) replyRoom.get(0)).getJid(); ++ } ++ ++ /** ++ * Returns true if the received packet should not be replied. Use ++ * {@link MultipleRecipientManager#reply(org.jivesoftware.smack.Connection, org.jivesoftware.smack.packet.Message, org.jivesoftware.smack.packet.Message)} ++ * to send replies. ++ * ++ * @return true if the received packet should not be replied. ++ */ ++ public boolean shouldNotReply() { ++ return !extension.getAddressesOfType(MultipleAddresses.NO_REPLY).isEmpty(); ++ } ++ ++ /** ++ * Returns the address to which all replies are requested to be sent or <tt>null</tt> if ++ * no specific address was provided. When no specific address was provided then the reply ++ * can be sent to any or all recipients. ++ * ++ * @return the address to which all replies are requested to be sent or <tt>null</tt> if ++ * no specific address was provided. ++ */ ++ public MultipleAddresses.Address getReplyAddress() { ++ List replyTo = extension.getAddressesOfType(MultipleAddresses.REPLY_TO); ++ return replyTo.isEmpty() ? null : (MultipleAddresses.Address) replyTo.get(0); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/MultipleRecipientManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MultipleRecipientManager.java +new file mode 100644 +index 0000000..e8de33a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MultipleRecipientManager.java +@@ -0,0 +1,353 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.packet.Message; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.util.Cache; ++import org.jivesoftware.smack.util.StringUtils; ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++import org.jivesoftware.smackx.packet.DiscoverItems; ++import org.jivesoftware.smackx.packet.MultipleAddresses; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++ ++/** ++ * A MultipleRecipientManager allows to send packets to multiple recipients by making use of ++ * <a href="http://www.jabber.org/jeps/jep-0033.html">JEP-33: Extended Stanza Addressing</a>. ++ * It also allows to send replies to packets that were sent to multiple recipients. ++ * ++ * @author Gaston Dombiak ++ */ ++public class MultipleRecipientManager { ++ ++ /** ++ * Create a cache to hold the 100 most recently accessed elements for a period of ++ * 24 hours. ++ */ ++ private static Cache services = new Cache(100, 24 * 60 * 60 * 1000); ++ ++ /** ++ * Sends the specified packet to the list of specified recipients using the ++ * specified connection. If the server has support for JEP-33 then only one ++ * packet is going to be sent to the server with the multiple recipient instructions. ++ * However, if JEP-33 is not supported by the server then the client is going to send ++ * the packet to each recipient. ++ * ++ * @param connection the connection to use to send the packet. ++ * @param packet the packet to send to the list of recipients. ++ * @param to the list of JIDs to include in the TO list or <tt>null</tt> if no TO ++ * list exists. ++ * @param cc the list of JIDs to include in the CC list or <tt>null</tt> if no CC ++ * list exists. ++ * @param bcc the list of JIDs to include in the BCC list or <tt>null</tt> if no BCC ++ * list exists. ++ * @throws XMPPException if server does not support JEP-33: Extended Stanza Addressing and ++ * some JEP-33 specific features were requested. ++ */ ++ public static void send(Connection connection, Packet packet, List to, List cc, List bcc) ++ throws XMPPException { ++ send(connection, packet, to, cc, bcc, null, null, false); ++ } ++ ++ /** ++ * Sends the specified packet to the list of specified recipients using the ++ * specified connection. If the server has support for JEP-33 then only one ++ * packet is going to be sent to the server with the multiple recipient instructions. ++ * However, if JEP-33 is not supported by the server then the client is going to send ++ * the packet to each recipient. ++ * ++ * @param connection the connection to use to send the packet. ++ * @param packet the packet to send to the list of recipients. ++ * @param to the list of JIDs to include in the TO list or <tt>null</tt> if no TO ++ * list exists. ++ * @param cc the list of JIDs to include in the CC list or <tt>null</tt> if no CC ++ * list exists. ++ * @param bcc the list of JIDs to include in the BCC list or <tt>null</tt> if no BCC ++ * list exists. ++ * @param replyTo address to which all replies are requested to be sent or <tt>null</tt> ++ * indicating that they can reply to any address. ++ * @param replyRoom JID of a MUC room to which responses should be sent or <tt>null</tt> ++ * indicating that they can reply to any address. ++ * @param noReply true means that receivers should not reply to the message. ++ * @throws XMPPException if server does not support JEP-33: Extended Stanza Addressing and ++ * some JEP-33 specific features were requested. ++ */ ++ public static void send(Connection connection, Packet packet, List to, List cc, List bcc, ++ String replyTo, String replyRoom, boolean noReply) throws XMPPException { ++ String serviceAddress = getMultipleRecipienServiceAddress(connection); ++ if (serviceAddress != null) { ++ // Send packet to target users using multiple recipient service provided by the server ++ sendThroughService(connection, packet, to, cc, bcc, replyTo, replyRoom, noReply, ++ serviceAddress); ++ } ++ else { ++ // Server does not support JEP-33 so try to send the packet to each recipient ++ if (noReply || (replyTo != null && replyTo.trim().length() > 0) || ++ (replyRoom != null && replyRoom.trim().length() > 0)) { ++ // Some specified JEP-33 features were requested so throw an exception alerting ++ // the user that this features are not available ++ throw new XMPPException("Extended Stanza Addressing not supported by server"); ++ } ++ // Send the packet to each individual recipient ++ sendToIndividualRecipients(connection, packet, to, cc, bcc); ++ } ++ } ++ ++ /** ++ * Sends a reply to a previously received packet that was sent to multiple recipients. Before ++ * attempting to send the reply message some checkings are performed. If any of those checkings ++ * fail then an XMPPException is going to be thrown with the specific error detail. ++ * ++ * @param connection the connection to use to send the reply. ++ * @param original the previously received packet that was sent to multiple recipients. ++ * @param reply the new message to send as a reply. ++ * @throws XMPPException if the original message was not sent to multiple recipients, or the ++ * original message cannot be replied or reply should be sent to a room. ++ */ ++ public static void reply(Connection connection, Message original, Message reply) ++ throws XMPPException { ++ MultipleRecipientInfo info = getMultipleRecipientInfo(original); ++ if (info == null) { ++ throw new XMPPException("Original message does not contain multiple recipient info"); ++ } ++ if (info.shouldNotReply()) { ++ throw new XMPPException("Original message should not be replied"); ++ } ++ if (info.getReplyRoom() != null) { ++ throw new XMPPException("Reply should be sent through a room"); ++ } ++ // Any <thread/> element from the initial message MUST be copied into the reply. ++ if (original.getThread() != null) { ++ reply.setThread(original.getThread()); ++ } ++ MultipleAddresses.Address replyAddress = info.getReplyAddress(); ++ if (replyAddress != null && replyAddress.getJid() != null) { ++ // Send reply to the reply_to address ++ reply.setTo(replyAddress.getJid()); ++ connection.sendPacket(reply); ++ } ++ else { ++ // Send reply to multiple recipients ++ List to = new ArrayList(); ++ List cc = new ArrayList(); ++ for (Iterator it = info.getTOAddresses().iterator(); it.hasNext();) { ++ String jid = ((MultipleAddresses.Address) it.next()).getJid(); ++ to.add(jid); ++ } ++ for (Iterator it = info.getCCAddresses().iterator(); it.hasNext();) { ++ String jid = ((MultipleAddresses.Address) it.next()).getJid(); ++ cc.add(jid); ++ } ++ // Add original sender as a 'to' address (if not already present) ++ if (!to.contains(original.getFrom()) && !cc.contains(original.getFrom())) { ++ to.add(original.getFrom()); ++ } ++ // Remove the sender from the TO/CC list (try with bare JID too) ++ String from = connection.getUser(); ++ if (!to.remove(from) && !cc.remove(from)) { ++ String bareJID = StringUtils.parseBareAddress(from); ++ to.remove(bareJID); ++ cc.remove(bareJID); ++ } ++ ++ String serviceAddress = getMultipleRecipienServiceAddress(connection); ++ if (serviceAddress != null) { ++ // Send packet to target users using multiple recipient service provided by the server ++ sendThroughService(connection, reply, to, cc, null, null, null, false, ++ serviceAddress); ++ } ++ else { ++ // Server does not support JEP-33 so try to send the packet to each recipient ++ sendToIndividualRecipients(connection, reply, to, cc, null); ++ } ++ } ++ } ++ ++ /** ++ * Returns the {@link MultipleRecipientInfo} contained in the specified packet or ++ * <tt>null</tt> if none was found. Only packets sent to multiple recipients will ++ * contain such information. ++ * ++ * @param packet the packet to check. ++ * @return the MultipleRecipientInfo contained in the specified packet or <tt>null</tt> ++ * if none was found. ++ */ ++ public static MultipleRecipientInfo getMultipleRecipientInfo(Packet packet) { ++ MultipleAddresses extension = (MultipleAddresses) packet ++ .getExtension("addresses", "http://jabber.org/protocol/address"); ++ return extension == null ? null : new MultipleRecipientInfo(extension); ++ } ++ ++ private static void sendToIndividualRecipients(Connection connection, Packet packet, ++ List to, List cc, List bcc) { ++ if (to != null) { ++ for (Iterator it = to.iterator(); it.hasNext();) { ++ String jid = (String) it.next(); ++ packet.setTo(jid); ++ connection.sendPacket(new PacketCopy(packet.toXML())); ++ } ++ } ++ if (cc != null) { ++ for (Iterator it = cc.iterator(); it.hasNext();) { ++ String jid = (String) it.next(); ++ packet.setTo(jid); ++ connection.sendPacket(new PacketCopy(packet.toXML())); ++ } ++ } ++ if (bcc != null) { ++ for (Iterator it = bcc.iterator(); it.hasNext();) { ++ String jid = (String) it.next(); ++ packet.setTo(jid); ++ connection.sendPacket(new PacketCopy(packet.toXML())); ++ } ++ } ++ } ++ ++ private static void sendThroughService(Connection connection, Packet packet, List to, ++ List cc, List bcc, String replyTo, String replyRoom, boolean noReply, ++ String serviceAddress) { ++ // Create multiple recipient extension ++ MultipleAddresses multipleAddresses = new MultipleAddresses(); ++ if (to != null) { ++ for (Iterator it = to.iterator(); it.hasNext();) { ++ String jid = (String) it.next(); ++ multipleAddresses.addAddress(MultipleAddresses.TO, jid, null, null, false, null); ++ } ++ } ++ if (cc != null) { ++ for (Iterator it = cc.iterator(); it.hasNext();) { ++ String jid = (String) it.next(); ++ multipleAddresses.addAddress(MultipleAddresses.CC, jid, null, null, false, null); ++ } ++ } ++ if (bcc != null) { ++ for (Iterator it = bcc.iterator(); it.hasNext();) { ++ String jid = (String) it.next(); ++ multipleAddresses.addAddress(MultipleAddresses.BCC, jid, null, null, false, null); ++ } ++ } ++ if (noReply) { ++ multipleAddresses.setNoReply(); ++ } ++ else { ++ if (replyTo != null && replyTo.trim().length() > 0) { ++ multipleAddresses ++ .addAddress(MultipleAddresses.REPLY_TO, replyTo, null, null, false, null); ++ } ++ if (replyRoom != null && replyRoom.trim().length() > 0) { ++ multipleAddresses.addAddress(MultipleAddresses.REPLY_ROOM, replyRoom, null, null, ++ false, null); ++ } ++ } ++ // Set the multiple recipient service address as the target address ++ packet.setTo(serviceAddress); ++ // Add extension to packet ++ packet.addExtension(multipleAddresses); ++ // Send the packet ++ connection.sendPacket(packet); ++ } ++ ++ /** ++ * Returns the address of the multiple recipients service. To obtain such address service ++ * discovery is going to be used on the connected server and if none was found then another ++ * attempt will be tried on the server items. The discovered information is going to be ++ * cached for 24 hours. ++ * ++ * @param connection the connection to use for disco. The connected server is going to be ++ * queried. ++ * @return the address of the multiple recipients service or <tt>null</tt> if none was found. ++ */ ++ private static String getMultipleRecipienServiceAddress(Connection connection) { ++ String serviceName = connection.getServiceName(); ++ String serviceAddress = (String) services.get(serviceName); ++ if (serviceAddress == null) { ++ synchronized (services) { ++ serviceAddress = (String) services.get(serviceName); ++ if (serviceAddress == null) { ++ ++ // Send the disco packet to the server itself ++ try { ++ DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection) ++ .discoverInfo(serviceName); ++ // Check if the server supports JEP-33 ++ if (info.containsFeature("http://jabber.org/protocol/address")) { ++ serviceAddress = serviceName; ++ } ++ else { ++ // Get the disco items and send the disco packet to each server item ++ DiscoverItems items = ServiceDiscoveryManager.getInstanceFor(connection) ++ .discoverItems(serviceName); ++ for (Iterator it = items.getItems(); it.hasNext();) { ++ DiscoverItems.Item item = (DiscoverItems.Item) it.next(); ++ info = ServiceDiscoveryManager.getInstanceFor(connection) ++ .discoverInfo(item.getEntityID(), item.getNode()); ++ if (info.containsFeature("http://jabber.org/protocol/address")) { ++ serviceAddress = serviceName; ++ break; ++ } ++ } ++ ++ } ++ // Cache the discovered information ++ services.put(serviceName, serviceAddress == null ? "" : serviceAddress); ++ } ++ catch (XMPPException e) { ++ e.printStackTrace(); ++ } ++ } ++ } ++ } ++ ++ return "".equals(serviceAddress) ? null : serviceAddress; ++ } ++ ++ /** ++ * Packet that holds the XML stanza to send. This class is useful when the same packet ++ * is needed to be sent to different recipients. Since using the same packet is not possible ++ * (i.e. cannot change the TO address of a queues packet to be sent) then this class was ++ * created to keep the XML stanza to send. ++ */ ++ private static class PacketCopy extends Packet { ++ ++ private String text; ++ ++ /** ++ * Create a copy of a packet with the text to send. The passed text must be a valid text to ++ * send to the server, no validation will be done on the passed text. ++ * ++ * @param text the whole text of the packet to send ++ */ ++ public PacketCopy(String text) { ++ this.text = text; ++ } ++ ++ public String toXML() { ++ return text; ++ } ++ ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/NodeInformationProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/NodeInformationProvider.java +new file mode 100644 +index 0000000..816be4d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/NodeInformationProvider.java +@@ -0,0 +1,68 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++import org.jivesoftware.smackx.packet.DiscoverItems; ++ ++import java.util.List; ++ ++ ++/** ++ * The NodeInformationProvider is responsible for providing supported indentities, features ++ * and hosted items (i.e. DiscoverItems.Item) about a given node. This information will be ++ * requested each time this XMPPP client receives a disco info or items requests on the ++ * given node. each time this XMPPP client receives a disco info or items requests on the ++ * given node. ++ * ++ * @author Gaston Dombiak ++ */ ++public interface NodeInformationProvider { ++ ++ /** ++ * Returns a list of the Items {@link org.jivesoftware.smackx.packet.DiscoverItems.Item} ++ * defined in the node. For example, the MUC protocol specifies that an XMPP client should ++ * answer an Item for each joined room when asked for the rooms where the use has joined. ++ * ++ * @return a list of the Items defined in the node. ++ */ ++ public abstract List<DiscoverItems.Item> getNodeItems(); ++ ++ /** ++ * Returns a list of the features defined in the node. For ++ * example, the entity caps protocol specifies that an XMPP client ++ * should answer with each feature supported by the client version ++ * or extension. ++ * ++ * @return a list of the feature strings defined in the node. ++ */ ++ public abstract List<String> getNodeFeatures(); ++ ++ /** ++ * Returns a list of the indentites defined in the node. For ++ * example, the x-command protocol must provide an identity of ++ * category automation and type command-node for each command. ++ * ++ * @return a list of the Identities defined in the node. ++ */ ++ public abstract List<DiscoverInfo.Identity> getNodeIdentities(); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/OfflineMessageHeader.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/OfflineMessageHeader.java +new file mode 100644 +index 0000000..55fd149 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/OfflineMessageHeader.java +@@ -0,0 +1,85 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import org.jivesoftware.smackx.packet.DiscoverItems; ++ ++/** ++ * The OfflineMessageHeader holds header information of an offline message. The header ++ * information was retrieved using the {@link OfflineMessageManager} class.<p> ++ * ++ * Each offline message is identified by the target user of the offline message and a unique stamp. ++ * Use {@link OfflineMessageManager#getMessages(java.util.List)} to retrieve the whole message. ++ * ++ * @author Gaston Dombiak ++ */ ++public class OfflineMessageHeader { ++ /** ++ * Bare JID of the user that was offline when the message was sent. ++ */ ++ private String user; ++ /** ++ * Full JID of the user that sent the message. ++ */ ++ private String jid; ++ /** ++ * Stamp that uniquely identifies the offline message. This stamp will be used for ++ * getting the specific message or delete it. The stamp may be of the form UTC timestamps ++ * but it is not required to have that format. ++ */ ++ private String stamp; ++ ++ public OfflineMessageHeader(DiscoverItems.Item item) { ++ super(); ++ user = item.getEntityID(); ++ jid = item.getName(); ++ stamp = item.getNode(); ++ } ++ ++ /** ++ * Returns the bare JID of the user that was offline when the message was sent. ++ * ++ * @return the bare JID of the user that was offline when the message was sent. ++ */ ++ public String getUser() { ++ return user; ++ } ++ ++ /** ++ * Returns the full JID of the user that sent the message. ++ * ++ * @return the full JID of the user that sent the message. ++ */ ++ public String getJid() { ++ return jid; ++ } ++ ++ /** ++ * Returns the stamp that uniquely identifies the offline message. This stamp will ++ * be used for getting the specific message or delete it. The stamp may be of the ++ * form UTC timestamps but it is not required to have that format. ++ * ++ * @return the stamp that uniquely identifies the offline message. ++ */ ++ public String getStamp() { ++ return stamp; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/OfflineMessageManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/OfflineMessageManager.java +new file mode 100644 +index 0000000..ea1c3ca +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/OfflineMessageManager.java +@@ -0,0 +1,284 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import org.jivesoftware.smack.PacketCollector; ++import org.jivesoftware.smack.SmackConfiguration; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.*; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Message; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++import org.jivesoftware.smackx.packet.DiscoverItems; ++import org.jivesoftware.smackx.packet.OfflineMessageInfo; ++import org.jivesoftware.smackx.packet.OfflineMessageRequest; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++ ++/** ++ * The OfflineMessageManager helps manage offline messages even before the user has sent an ++ * available presence. When a user asks for his offline messages before sending an available ++ * presence then the server will not send a flood with all the offline messages when the user ++ * becomes online. The server will not send a flood with all the offline messages to the session ++ * that made the offline messages request or to any other session used by the user that becomes ++ * online.<p> ++ * ++ * Once the session that made the offline messages request has been closed and the user becomes ++ * offline in all the resources then the server will resume storing the messages offline and will ++ * send all the offline messages to the user when he becomes online. Therefore, the server will ++ * flood the user when he becomes online unless the user uses this class to manage his offline ++ * messages. ++ * ++ * @author Gaston Dombiak ++ */ ++public class OfflineMessageManager { ++ ++ private final static String namespace = "http://jabber.org/protocol/offline"; ++ ++ private Connection connection; ++ ++ private PacketFilter packetFilter; ++ ++ public OfflineMessageManager(Connection connection) { ++ this.connection = connection; ++ packetFilter = ++ new AndFilter(new PacketExtensionFilter("offline", namespace), ++ new PacketTypeFilter(Message.class)); ++ } ++ ++ /** ++ * Returns true if the server supports Flexible Offline Message Retrieval. When the server ++ * supports Flexible Offline Message Retrieval it is possible to get the header of the offline ++ * messages, get specific messages, delete specific messages, etc. ++ * ++ * @return a boolean indicating if the server supports Flexible Offline Message Retrieval. ++ * @throws XMPPException If the user is not allowed to make this request. ++ */ ++ public boolean supportsFlexibleRetrieval() throws XMPPException { ++ DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(connection.getServiceName()); ++ return info.containsFeature(namespace); ++ } ++ ++ /** ++ * Returns the number of offline messages for the user of the connection. ++ * ++ * @return the number of offline messages for the user of the connection. ++ * @throws XMPPException If the user is not allowed to make this request or the server does ++ * not support offline message retrieval. ++ */ ++ public int getMessageCount() throws XMPPException { ++ DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(null, ++ namespace); ++ Form extendedInfo = Form.getFormFrom(info); ++ if (extendedInfo != null) { ++ String value = extendedInfo.getField("number_of_messages").getValues().next(); ++ return Integer.parseInt(value); ++ } ++ return 0; ++ } ++ ++ /** ++ * Returns an iterator on <tt>OfflineMessageHeader</tt> that keep information about the ++ * offline message. The OfflineMessageHeader includes a stamp that could be used to retrieve ++ * the complete message or delete the specific message. ++ * ++ * @return an iterator on <tt>OfflineMessageHeader</tt> that keep information about the offline ++ * message. ++ * @throws XMPPException If the user is not allowed to make this request or the server does ++ * not support offline message retrieval. ++ */ ++ public Iterator<OfflineMessageHeader> getHeaders() throws XMPPException { ++ List<OfflineMessageHeader> answer = new ArrayList<OfflineMessageHeader>(); ++ DiscoverItems items = ServiceDiscoveryManager.getInstanceFor(connection).discoverItems( ++ null, namespace); ++ for (Iterator it = items.getItems(); it.hasNext();) { ++ DiscoverItems.Item item = (DiscoverItems.Item) it.next(); ++ answer.add(new OfflineMessageHeader(item)); ++ } ++ return answer.iterator(); ++ } ++ ++ /** ++ * Returns an Iterator with the offline <tt>Messages</tt> whose stamp matches the specified ++ * request. The request will include the list of stamps that uniquely identifies ++ * the offline messages to retrieve. The returned offline messages will not be deleted ++ * from the server. Use {@link #deleteMessages(java.util.List)} to delete the messages. ++ * ++ * @param nodes the list of stamps that uniquely identifies offline message. ++ * @return an Iterator with the offline <tt>Messages</tt> that were received as part of ++ * this request. ++ * @throws XMPPException If the user is not allowed to make this request or the server does ++ * not support offline message retrieval. ++ */ ++ public Iterator<Message> getMessages(final List<String> nodes) throws XMPPException { ++ List<Message> messages = new ArrayList<Message>(); ++ OfflineMessageRequest request = new OfflineMessageRequest(); ++ for (String node : nodes) { ++ OfflineMessageRequest.Item item = new OfflineMessageRequest.Item(node); ++ item.setAction("view"); ++ request.addItem(item); ++ } ++ // Filter packets looking for an answer from the server. ++ PacketFilter responseFilter = new PacketIDFilter(request.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Filter offline messages that were requested by this request ++ PacketFilter messageFilter = new AndFilter(packetFilter, new PacketFilter() { ++ public boolean accept(Packet packet) { ++ OfflineMessageInfo info = (OfflineMessageInfo) packet.getExtension("offline", ++ namespace); ++ return nodes.contains(info.getNode()); ++ } ++ }); ++ PacketCollector messageCollector = connection.createPacketCollector(messageFilter); ++ // Send the retrieval request to the server. ++ connection.sendPacket(request); ++ // Wait up to a certain number of seconds for a reply. ++ IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (answer == null) { ++ throw new XMPPException("No response from server."); ++ } else if (answer.getError() != null) { ++ throw new XMPPException(answer.getError()); ++ } ++ ++ // Collect the received offline messages ++ Message message = (Message) messageCollector.nextResult( ++ SmackConfiguration.getPacketReplyTimeout()); ++ while (message != null) { ++ messages.add(message); ++ message = ++ (Message) messageCollector.nextResult( ++ SmackConfiguration.getPacketReplyTimeout()); ++ } ++ // Stop queuing offline messages ++ messageCollector.cancel(); ++ return messages.iterator(); ++ } ++ ++ /** ++ * Returns an Iterator with all the offline <tt>Messages</tt> of the user. The returned offline ++ * messages will not be deleted from the server. Use {@link #deleteMessages(java.util.List)} ++ * to delete the messages. ++ * ++ * @return an Iterator with all the offline <tt>Messages</tt> of the user. ++ * @throws XMPPException If the user is not allowed to make this request or the server does ++ * not support offline message retrieval. ++ */ ++ public Iterator<Message> getMessages() throws XMPPException { ++ List<Message> messages = new ArrayList<Message>(); ++ OfflineMessageRequest request = new OfflineMessageRequest(); ++ request.setFetch(true); ++ // Filter packets looking for an answer from the server. ++ PacketFilter responseFilter = new PacketIDFilter(request.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Filter offline messages that were requested by this request ++ PacketCollector messageCollector = connection.createPacketCollector(packetFilter); ++ // Send the retrieval request to the server. ++ connection.sendPacket(request); ++ // Wait up to a certain number of seconds for a reply. ++ IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (answer == null) { ++ throw new XMPPException("No response from server."); ++ } else if (answer.getError() != null) { ++ throw new XMPPException(answer.getError()); ++ } ++ ++ // Collect the received offline messages ++ Message message = (Message) messageCollector.nextResult( ++ SmackConfiguration.getPacketReplyTimeout()); ++ while (message != null) { ++ messages.add(message); ++ message = ++ (Message) messageCollector.nextResult( ++ SmackConfiguration.getPacketReplyTimeout()); ++ } ++ // Stop queuing offline messages ++ messageCollector.cancel(); ++ return messages.iterator(); ++ } ++ ++ /** ++ * Deletes the specified list of offline messages. The request will include the list of ++ * stamps that uniquely identifies the offline messages to delete. ++ * ++ * @param nodes the list of stamps that uniquely identifies offline message. ++ * @throws XMPPException If the user is not allowed to make this request or the server does ++ * not support offline message retrieval. ++ */ ++ public void deleteMessages(List<String> nodes) throws XMPPException { ++ OfflineMessageRequest request = new OfflineMessageRequest(); ++ for (String node : nodes) { ++ OfflineMessageRequest.Item item = new OfflineMessageRequest.Item(node); ++ item.setAction("remove"); ++ request.addItem(item); ++ } ++ // Filter packets looking for an answer from the server. ++ PacketFilter responseFilter = new PacketIDFilter(request.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Send the deletion request to the server. ++ connection.sendPacket(request); ++ // Wait up to a certain number of seconds for a reply. ++ IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (answer == null) { ++ throw new XMPPException("No response from server."); ++ } else if (answer.getError() != null) { ++ throw new XMPPException(answer.getError()); ++ } ++ } ++ ++ /** ++ * Deletes all offline messages of the user. ++ * ++ * @throws XMPPException If the user is not allowed to make this request or the server does ++ * not support offline message retrieval. ++ */ ++ public void deleteMessages() throws XMPPException { ++ OfflineMessageRequest request = new OfflineMessageRequest(); ++ request.setPurge(true); ++ // Filter packets looking for an answer from the server. ++ PacketFilter responseFilter = new PacketIDFilter(request.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Send the deletion request to the server. ++ connection.sendPacket(request); ++ // Wait up to a certain number of seconds for a reply. ++ IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (answer == null) { ++ throw new XMPPException("No response from server."); ++ } else if (answer.getError() != null) { ++ throw new XMPPException(answer.getError()); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/PEPListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/PEPListener.java +new file mode 100644 +index 0000000..1d39484 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/PEPListener.java +@@ -0,0 +1,42 @@ ++/** ++ * $RCSfile: PEPListener.java,v $ ++ * $Revision: 1.1 $ ++ * $Date: 2007/11/03 00:14:32 $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import org.jivesoftware.smackx.packet.PEPEvent; ++ ++ ++/** ++ * ++ * A listener that is fired anytime a PEP event message is received. ++ * ++ * @author Jeff Williams ++ */ ++public interface PEPListener { ++ ++ /** ++ * Called when PEP events are received as part of a presence subscribe or message filter. ++ * ++ * @param from the user that sent the entries. ++ * @param event the event contained in the message. ++ */ ++ public void eventReceived(String from, PEPEvent event); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/PEPManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/PEPManager.java +new file mode 100644 +index 0000000..857f1c4 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/PEPManager.java +@@ -0,0 +1,160 @@ ++/** ++ * $RCSfile: PEPManager.java,v $ ++ * $Revision: 1.4 $ ++ * $Date: 2007/11/06 21:43:40 $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.filter.PacketExtensionFilter; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.packet.Message; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.IQ.Type; ++import org.jivesoftware.smackx.packet.PEPEvent; ++import org.jivesoftware.smackx.packet.PEPItem; ++import org.jivesoftware.smackx.packet.PEPPubSub; ++ ++/** ++ * ++ * Manages Personal Event Publishing (XEP-163). A PEPManager provides a high level access to ++ * pubsub personal events. It also provides an easy way ++ * to hook up custom logic when events are received from another XMPP client through PEPListeners. ++ * ++ * Use example: ++ * ++ * <pre> ++ * PEPManager pepManager = new PEPManager(smackConnection); ++ * pepManager.addPEPListener(new PEPListener() { ++ * public void eventReceived(String inFrom, PEPEvent inEvent) { ++ * LOGGER.debug("Event received: " + inEvent); ++ * } ++ * }); ++ * ++ * PEPProvider pepProvider = new PEPProvider(); ++ * pepProvider.registerPEPParserExtension("http://jabber.org/protocol/tune", new TuneProvider()); ++ * ProviderManager.getInstance().addExtensionProvider("event", "http://jabber.org/protocol/pubsub#event", pepProvider); ++ * ++ * Tune tune = new Tune("jeff", "1", "CD", "My Title", "My Track"); ++ * pepManager.publish(tune); ++ * </pre> ++ * ++ * @author Jeff Williams ++ */ ++public class PEPManager { ++ ++ private List<PEPListener> pepListeners = new ArrayList<PEPListener>(); ++ ++ private Connection connection; ++ ++ private PacketFilter packetFilter = new PacketExtensionFilter("event", "http://jabber.org/protocol/pubsub#event"); ++ private PacketListener packetListener; ++ ++ /** ++ * Creates a new PEP exchange manager. ++ * ++ * @param connection a Connection which is used to send and receive messages. ++ */ ++ public PEPManager(Connection connection) { ++ this.connection = connection; ++ init(); ++ } ++ ++ /** ++ * Adds a listener to PEPs. The listener will be fired anytime PEP events ++ * are received from remote XMPP clients. ++ * ++ * @param pepListener a roster exchange listener. ++ */ ++ public void addPEPListener(PEPListener pepListener) { ++ synchronized (pepListeners) { ++ if (!pepListeners.contains(pepListener)) { ++ pepListeners.add(pepListener); ++ } ++ } ++ } ++ ++ /** ++ * Removes a listener from PEP events. ++ * ++ * @param pepListener a roster exchange listener. ++ */ ++ public void removePEPListener(PEPListener pepListener) { ++ synchronized (pepListeners) { ++ pepListeners.remove(pepListener); ++ } ++ } ++ ++ /** ++ * Publish an event. ++ * ++ * @param item the item to publish. ++ */ ++ public void publish(PEPItem item) { ++ // Create a new message to publish the event. ++ PEPPubSub pubSub = new PEPPubSub(item); ++ pubSub.setType(Type.SET); ++ //pubSub.setFrom(connection.getUser()); ++ ++ // Send the message that contains the roster ++ connection.sendPacket(pubSub); ++ } ++ ++ /** ++ * Fires roster exchange listeners. ++ */ ++ private void firePEPListeners(String from, PEPEvent event) { ++ PEPListener[] listeners = null; ++ synchronized (pepListeners) { ++ listeners = new PEPListener[pepListeners.size()]; ++ pepListeners.toArray(listeners); ++ } ++ for (int i = 0; i < listeners.length; i++) { ++ listeners[i].eventReceived(from, event); ++ } ++ } ++ ++ private void init() { ++ // Listens for all roster exchange packets and fire the roster exchange listeners. ++ packetListener = new PacketListener() { ++ public void processPacket(Packet packet) { ++ Message message = (Message) packet; ++ PEPEvent event = (PEPEvent) message.getExtension("event", "http://jabber.org/protocol/pubsub#event"); ++ // Fire event for roster exchange listeners ++ firePEPListeners(message.getFrom(), event); ++ }; ++ ++ }; ++ connection.addPacketListener(packetListener, packetFilter); ++ } ++ ++ public void destroy() { ++ if (connection != null) ++ connection.removePacketListener(packetListener); ++ ++ } ++ ++ protected void finalize() throws Throwable { ++ destroy(); ++ super.finalize(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/PrivateDataManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/PrivateDataManager.java +new file mode 100644 +index 0000000..03ba448 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/PrivateDataManager.java +@@ -0,0 +1,359 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import org.jivesoftware.smack.PacketCollector; ++import org.jivesoftware.smack.SmackConfiguration; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smackx.packet.DefaultPrivateData; ++import org.jivesoftware.smackx.packet.PrivateData; ++import org.jivesoftware.smackx.provider.PrivateDataProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.util.Hashtable; ++import java.util.Map; ++ ++/** ++ * Manages private data, which is a mechanism to allow users to store arbitrary XML ++ * data on an XMPP server. Each private data chunk is defined by a element name and ++ * XML namespace. Example private data: ++ * ++ * <pre> ++ * <color xmlns="http://example.com/xmpp/color"> ++ * <favorite>blue</blue> ++ * <leastFavorite>puce</leastFavorite> ++ * </color> ++ * </pre> ++ * ++ * {@link PrivateDataProvider} instances are responsible for translating the XML into objects. ++ * If no PrivateDataProvider is registered for a given element name and namespace, then ++ * a {@link DefaultPrivateData} instance will be returned.<p> ++ * ++ * Warning: this is an non-standard protocol documented by ++ * <a href="http://www.jabber.org/jeps/jep-0049.html">JEP-49</a>. Because this is a ++ * non-standard protocol, it is subject to change. ++ * ++ * @author Matt Tucker ++ */ ++public class PrivateDataManager { ++ ++ /** ++ * Map of provider instances. ++ */ ++ private static Map privateDataProviders = new Hashtable(); ++ ++ /** ++ * Returns the private data provider registered to the specified XML element name and namespace. ++ * For example, if a provider was registered to the element name "prefs" and the ++ * namespace "http://www.xmppclient.com/prefs", then the following packet would trigger ++ * the provider: ++ * ++ * <pre> ++ * <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'> ++ * <query xmlns='jabber:iq:private'> ++ * <prefs xmlns='http://www.xmppclient.com/prefs'> ++ * <value1>ABC</value1> ++ * <value2>XYZ</value2> ++ * </prefs> ++ * </query> ++ * </iq></pre> ++ * ++ * <p>Note: this method is generally only called by the internal Smack classes. ++ * ++ * @param elementName the XML element name. ++ * @param namespace the XML namespace. ++ * @return the PrivateData provider. ++ */ ++ public static PrivateDataProvider getPrivateDataProvider(String elementName, String namespace) { ++ String key = getProviderKey(elementName, namespace); ++ return (PrivateDataProvider)privateDataProviders.get(key); ++ } ++ ++ /** ++ * Adds a private data provider with the specified element name and name space. The provider ++ * will override any providers loaded through the classpath. ++ * ++ * @param elementName the XML element name. ++ * @param namespace the XML namespace. ++ * @param provider the private data provider. ++ */ ++ public static void addPrivateDataProvider(String elementName, String namespace, ++ PrivateDataProvider provider) ++ { ++ String key = getProviderKey(elementName, namespace); ++ privateDataProviders.put(key, provider); ++ } ++ ++ /** ++ * Removes a private data provider with the specified element name and namespace. ++ * ++ * @param elementName The XML element name. ++ * @param namespace The XML namespace. ++ */ ++ public static void removePrivateDataProvider(String elementName, String namespace) { ++ String key = getProviderKey(elementName, namespace); ++ privateDataProviders.remove(key); ++ } ++ ++ ++ private Connection connection; ++ ++ /** ++ * The user to get and set private data for. In most cases, this value should ++ * be <tt>null</tt>, as the typical use of private data is to get and set ++ * your own private data and not others. ++ */ ++ private String user; ++ ++ /** ++ * Creates a new private data manager. The connection must have ++ * undergone a successful login before being used to construct an instance of ++ * this class. ++ * ++ * @param connection an XMPP connection which must have already undergone a ++ * successful login. ++ */ ++ public PrivateDataManager(Connection connection) { ++ if (!connection.isAuthenticated()) { ++ throw new IllegalStateException("Must be logged in to XMPP server."); ++ } ++ this.connection = connection; ++ } ++ ++ /** ++ * Creates a new private data manager for a specific user (special case). Most ++ * servers only support getting and setting private data for the user that ++ * authenticated via the connection. However, some servers support the ability ++ * to get and set private data for other users (for example, if you are the ++ * administrator). The connection must have undergone a successful login before ++ * being used to construct an instance of this class. ++ * ++ * @param connection an XMPP connection which must have already undergone a ++ * successful login. ++ * @param user the XMPP address of the user to get and set private data for. ++ */ ++ public PrivateDataManager(Connection connection, String user) { ++ if (!connection.isAuthenticated()) { ++ throw new IllegalStateException("Must be logged in to XMPP server."); ++ } ++ this.connection = connection; ++ this.user = user; ++ } ++ ++ /** ++ * Returns the private data specified by the given element name and namespace. Each chunk ++ * of private data is uniquely identified by an element name and namespace pair.<p> ++ * ++ * If a PrivateDataProvider is registered for the specified element name/namespace pair then ++ * that provider will determine the specific object type that is returned. If no provider ++ * is registered, a {@link DefaultPrivateData} instance will be returned. ++ * ++ * @param elementName the element name. ++ * @param namespace the namespace. ++ * @return the private data. ++ * @throws XMPPException if an error occurs getting the private data. ++ */ ++ public PrivateData getPrivateData(final String elementName, final String namespace) ++ throws XMPPException ++ { ++ // Create an IQ packet to get the private data. ++ IQ privateDataGet = new IQ() { ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<query xmlns=\"jabber:iq:private\">"); ++ buf.append("<").append(elementName).append(" xmlns=\"").append(namespace).append("\"/>"); ++ buf.append("</query>"); ++ return buf.toString(); ++ } ++ }; ++ privateDataGet.setType(IQ.Type.GET); ++ // Address the packet to the other account if user has been set. ++ if (user != null) { ++ privateDataGet.setTo(user); ++ } ++ ++ // Setup a listener for the reply to the set operation. ++ String packetID = privateDataGet.getPacketID(); ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(packetID)); ++ ++ // Send the private data. ++ connection.sendPacket(privateDataGet); ++ ++ // Wait up to five seconds for a response from the server. ++ IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ // If the server replied with an error, throw an exception. ++ else if (response.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(response.getError()); ++ } ++ return ((PrivateDataResult)response).getPrivateData(); ++ } ++ ++ /** ++ * Sets a private data value. Each chunk of private data is uniquely identified by an ++ * element name and namespace pair. If private data has already been set with the ++ * element name and namespace, then the new private data will overwrite the old value. ++ * ++ * @param privateData the private data. ++ * @throws XMPPException if setting the private data fails. ++ */ ++ public void setPrivateData(final PrivateData privateData) throws XMPPException { ++ // Create an IQ packet to set the private data. ++ IQ privateDataSet = new IQ() { ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<query xmlns=\"jabber:iq:private\">"); ++ buf.append(privateData.toXML()); ++ buf.append("</query>"); ++ return buf.toString(); ++ } ++ }; ++ privateDataSet.setType(IQ.Type.SET); ++ // Address the packet to the other account if user has been set. ++ if (user != null) { ++ privateDataSet.setTo(user); ++ } ++ ++ // Setup a listener for the reply to the set operation. ++ String packetID = privateDataSet.getPacketID(); ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(packetID)); ++ ++ // Send the private data. ++ connection.sendPacket(privateDataSet); ++ ++ // Wait up to five seconds for a response from the server. ++ IQ response = (IQ)collector.nextResult(5000); ++ // Stop queuing results ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ // If the server replied with an error, throw an exception. ++ else if (response.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(response.getError()); ++ } ++ } ++ ++ /** ++ * Returns a String key for a given element name and namespace. ++ * ++ * @param elementName the element name. ++ * @param namespace the namespace. ++ * @return a unique key for the element name and namespace pair. ++ */ ++ private static String getProviderKey(String elementName, String namespace) { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(elementName).append("/><").append(namespace).append("/>"); ++ return buf.toString(); ++ } ++ ++ /** ++ * An IQ provider to parse IQ results containing private data. ++ */ ++ public static class PrivateDataIQProvider implements IQProvider { ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ PrivateData privateData = null; ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ String elementName = parser.getName(); ++ String namespace = parser.getNamespace(); ++ // See if any objects are registered to handle this private data type. ++ PrivateDataProvider provider = getPrivateDataProvider(elementName, namespace); ++ // If there is a registered provider, use it. ++ if (provider != null) { ++ privateData = provider.parsePrivateData(parser); ++ } ++ // Otherwise, use a DefaultPrivateData instance to store the private data. ++ else { ++ DefaultPrivateData data = new DefaultPrivateData(elementName, namespace); ++ boolean finished = false; ++ while (!finished) { ++ int event = parser.next(); ++ if (event == XmlPullParser.START_TAG) { ++ String name = parser.getName(); ++ // If an empty element, set the value with the empty string. ++ if (parser.isEmptyElementTag()) { ++ data.setValue(name,""); ++ } ++ // Otherwise, get the the element text. ++ else { ++ event = parser.next(); ++ if (event == XmlPullParser.TEXT) { ++ String value = parser.getText(); ++ data.setValue(name, value); ++ } ++ } ++ } ++ else if (event == XmlPullParser.END_TAG) { ++ if (parser.getName().equals(elementName)) { ++ finished = true; ++ } ++ } ++ } ++ privateData = data; ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("query")) { ++ done = true; ++ } ++ } ++ } ++ return new PrivateDataResult(privateData); ++ } ++ } ++ ++ /** ++ * An IQ packet to hold PrivateData GET results. ++ */ ++ private static class PrivateDataResult extends IQ { ++ ++ private PrivateData privateData; ++ ++ PrivateDataResult(PrivateData privateData) { ++ this.privateData = privateData; ++ } ++ ++ public PrivateData getPrivateData() { ++ return privateData; ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<query xmlns=\"jabber:iq:private\">"); ++ if (privateData != null) { ++ privateData.toXML(); ++ } ++ buf.append("</query>"); ++ return buf.toString(); ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/RemoteRosterEntry.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/RemoteRosterEntry.java +new file mode 100644 +index 0000000..acf0b0b +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/RemoteRosterEntry.java +@@ -0,0 +1,114 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import java.util.*; ++ ++/** ++ * Represents a roster item, which consists of a JID and , their name and ++ * the groups the roster item belongs to. This roster item does not belong ++ * to the local roster. Therefore, it does not persist in the server.<p> ++ * ++ * The idea of a RemoteRosterEntry is to be used as part of a roster exchange. ++ * ++ * @author Gaston Dombiak ++ */ ++public class RemoteRosterEntry { ++ ++ private String user; ++ private String name; ++ private final List<String> groupNames = new ArrayList<String>(); ++ ++ /** ++ * Creates a new remote roster entry. ++ * ++ * @param user the user. ++ * @param name the user's name. ++ * @param groups the list of group names the entry will belong to, or <tt>null</tt> if the ++ * the roster entry won't belong to a group. ++ */ ++ public RemoteRosterEntry(String user, String name, String [] groups) { ++ this.user = user; ++ this.name = name; ++ if (groups != null) { ++ groupNames.addAll(Arrays.asList(groups)); ++ } ++ } ++ ++ /** ++ * Returns the user. ++ * ++ * @return the user. ++ */ ++ public String getUser() { ++ return user; ++ } ++ ++ /** ++ * Returns the user's name. ++ * ++ * @return the user's name. ++ */ ++ public String getName() { ++ return name; ++ } ++ ++ /** ++ * Returns an Iterator for the group names (as Strings) that the roster entry ++ * belongs to. ++ * ++ * @return an Iterator for the group names. ++ */ ++ public Iterator getGroupNames() { ++ synchronized (groupNames) { ++ return Collections.unmodifiableList(groupNames).iterator(); ++ } ++ } ++ ++ /** ++ * Returns a String array for the group names that the roster entry ++ * belongs to. ++ * ++ * @return a String[] for the group names. ++ */ ++ public String[] getGroupArrayNames() { ++ synchronized (groupNames) { ++ return Collections.unmodifiableList(groupNames).toArray(new String[groupNames.size()]); ++ } ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<item jid=\"").append(user).append("\""); ++ if (name != null) { ++ buf.append(" name=\"").append(name).append("\""); ++ } ++ buf.append(">"); ++ synchronized (groupNames) { ++ for (String groupName : groupNames) { ++ buf.append("<group>").append(groupName).append("</group>"); ++ } ++ } ++ buf.append("</item>"); ++ return buf.toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/ReportedData.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ReportedData.java +new file mode 100644 +index 0000000..0d7b760 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ReportedData.java +@@ -0,0 +1,281 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.packet.DataForm; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++ ++/** ++ * Represents a set of data results returned as part of a search. The report is structured ++ * in columns and rows. ++ * ++ * @author Gaston Dombiak ++ */ ++public class ReportedData { ++ ++ private List<Column> columns = new ArrayList<Column>(); ++ private List<Row> rows = new ArrayList<Row>(); ++ private String title = ""; ++ ++ /** ++ * Returns a new ReportedData if the packet is used for reporting data and includes an ++ * extension that matches the elementName and namespace "x","jabber:x:data". ++ * ++ * @param packet the packet used for reporting data. ++ */ ++ public static ReportedData getReportedDataFrom(Packet packet) { ++ // Check if the packet includes the DataForm extension ++ PacketExtension packetExtension = packet.getExtension("x","jabber:x:data"); ++ if (packetExtension != null) { ++ // Check if the existing DataForm is a result of a search ++ DataForm dataForm = (DataForm) packetExtension; ++ if (dataForm.getReportedData() != null) ++ return new ReportedData(dataForm); ++ } ++ // Otherwise return null ++ return null; ++ } ++ ++ ++ /** ++ * Creates a new ReportedData based on the returned dataForm from a search ++ *(namespace "jabber:iq:search"). ++ * ++ * @param dataForm the dataForm returned from a search (namespace "jabber:iq:search"). ++ */ ++ private ReportedData(DataForm dataForm) { ++ // Add the columns to the report based on the reported data fields ++ for (Iterator fields = dataForm.getReportedData().getFields(); fields.hasNext();) { ++ FormField field = (FormField)fields.next(); ++ columns.add(new Column(field.getLabel(), field.getVariable(), field.getType())); ++ } ++ ++ // Add the rows to the report based on the form's items ++ for (Iterator items = dataForm.getItems(); items.hasNext();) { ++ DataForm.Item item = (DataForm.Item)items.next(); ++ List<Field> fieldList = new ArrayList<Field>(columns.size()); ++ FormField field; ++ for (Iterator fields = item.getFields(); fields.hasNext();) { ++ field = (FormField) fields.next(); ++ // The field is created with all the values of the data form's field ++ List<String> values = new ArrayList<String>(); ++ for (Iterator<String> it=field.getValues(); it.hasNext();) { ++ values.add(it.next()); ++ } ++ fieldList.add(new Field(field.getVariable(), values)); ++ } ++ rows.add(new Row(fieldList)); ++ } ++ ++ // Set the report's title ++ this.title = dataForm.getTitle(); ++ } ++ ++ ++ public ReportedData(){ ++ // Allow for model creation of ReportedData. ++ } ++ ++ /** ++ * Adds a new <code>Row</code>. ++ * @param row the new row to add. ++ */ ++ public void addRow(Row row){ ++ rows.add(row); ++ } ++ ++ /** ++ * Adds a new <code>Column</code> ++ * @param column the column to add. ++ */ ++ public void addColumn(Column column){ ++ columns.add(column); ++ } ++ ++ ++ /** ++ * Returns an Iterator for the rows returned from a search. ++ * ++ * @return an Iterator for the rows returned from a search. ++ */ ++ public Iterator<Row> getRows() { ++ return Collections.unmodifiableList(new ArrayList<Row>(rows)).iterator(); ++ } ++ ++ /** ++ * Returns an Iterator for the columns returned from a search. ++ * ++ * @return an Iterator for the columns returned from a search. ++ */ ++ public Iterator<Column> getColumns() { ++ return Collections.unmodifiableList(new ArrayList<Column>(columns)).iterator(); ++ } ++ ++ ++ /** ++ * Returns the report's title. It is similar to the title on a web page or an X ++ * window. ++ * ++ * @return title of the report. ++ */ ++ public String getTitle() { ++ return title; ++ } ++ ++ /** ++ * ++ * Represents the columns definition of the reported data. ++ * ++ * @author Gaston Dombiak ++ */ ++ public static class Column { ++ private String label; ++ private String variable; ++ private String type; ++ ++ /** ++ * Creates a new column with the specified definition. ++ * ++ * @param label the columns's label. ++ * @param variable the variable name of the column. ++ * @param type the format for the returned data. ++ */ ++ public Column(String label, String variable, String type) { ++ this.label = label; ++ this.variable = variable; ++ this.type = type; ++ } ++ ++ /** ++ * Returns the column's label. ++ * ++ * @return label of the column. ++ */ ++ public String getLabel() { ++ return label; ++ } ++ ++ ++ /** ++ * Returns the column's data format. Valid formats are: ++ * ++ * <ul> ++ * <li>text-single -> single line or word of text ++ * <li>text-private -> instead of showing the user what they typed, you show ***** to ++ * protect it ++ * <li>text-multi -> multiple lines of text entry ++ * <li>list-single -> given a list of choices, pick one ++ * <li>list-multi -> given a list of choices, pick one or more ++ * <li>boolean -> 0 or 1, true or false, yes or no. Default value is 0 ++ * <li>fixed -> fixed for putting in text to show sections, or just advertise your web ++ * site in the middle of the form ++ * <li>hidden -> is not given to the user at all, but returned with the questionnaire ++ * <li>jid-single -> Jabber ID - choosing a JID from your roster, and entering one based ++ * on the rules for a JID. ++ * <li>jid-multi -> multiple entries for JIDs ++ * </ul> ++ * ++ * @return format for the returned data. ++ */ ++ public String getType() { ++ return type; ++ } ++ ++ ++ /** ++ * Returns the variable name that the column is showing. ++ * ++ * @return the variable name of the column. ++ */ ++ public String getVariable() { ++ return variable; ++ } ++ ++ ++ } ++ ++ public static class Row { ++ private List<Field> fields = new ArrayList<Field>(); ++ ++ public Row(List<Field> fields) { ++ this.fields = fields; ++ } ++ ++ /** ++ * Returns the values of the field whose variable matches the requested variable. ++ * ++ * @param variable the variable to match. ++ * @return the values of the field whose variable matches the requested variable. ++ */ ++ public Iterator getValues(String variable) { ++ for(Iterator<Field> it=getFields();it.hasNext();) { ++ Field field = it.next(); ++ if (variable.equalsIgnoreCase(field.getVariable())) { ++ return field.getValues(); ++ } ++ } ++ return null; ++ } ++ ++ /** ++ * Returns the fields that define the data that goes with the item. ++ * ++ * @return the fields that define the data that goes with the item. ++ */ ++ private Iterator<Field> getFields() { ++ return Collections.unmodifiableList(new ArrayList<Field>(fields)).iterator(); ++ } ++ } ++ ++ public static class Field { ++ private String variable; ++ private List<String> values; ++ ++ public Field(String variable, List<String> values) { ++ this.variable = variable; ++ this.values = values; ++ } ++ ++ /** ++ * Returns the variable name that the field represents. ++ * ++ * @return the variable name of the field. ++ */ ++ public String getVariable() { ++ return variable; ++ } ++ ++ /** ++ * Returns an iterator on the values reported as part of the search. ++ * ++ * @return the returned values of the search. ++ */ ++ public Iterator<String> getValues() { ++ return Collections.unmodifiableList(values).iterator(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/RosterExchangeListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/RosterExchangeListener.java +new file mode 100644 +index 0000000..6c21458 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/RosterExchangeListener.java +@@ -0,0 +1,42 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import java.util.Iterator; ++ ++/** ++ * ++ * A listener that is fired anytime a roster exchange is received. ++ * ++ * @author Gaston Dombiak ++ */ ++public interface RosterExchangeListener { ++ ++ /** ++ * Called when roster entries are received as part of a roster exchange. ++ * ++ * @param from the user that sent the entries. ++ * @param remoteRosterEntries the entries sent by the user. The entries are instances of ++ * RemoteRosterEntry. ++ */ ++ public void entriesReceived(String from, Iterator remoteRosterEntries); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/RosterExchangeManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/RosterExchangeManager.java +new file mode 100644 +index 0000000..abb03ab +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/RosterExchangeManager.java +@@ -0,0 +1,187 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++ ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.Roster; ++import org.jivesoftware.smack.RosterEntry; ++import org.jivesoftware.smack.RosterGroup; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.filter.PacketExtensionFilter; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.packet.Message; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smackx.packet.RosterExchange; ++ ++/** ++ * ++ * Manages Roster exchanges. A RosterExchangeManager provides a high level access to send ++ * rosters, roster groups and roster entries to XMPP clients. It also provides an easy way ++ * to hook up custom logic when entries are received from another XMPP client through ++ * RosterExchangeListeners. ++ * ++ * @author Gaston Dombiak ++ */ ++public class RosterExchangeManager { ++ ++ private List<RosterExchangeListener> rosterExchangeListeners = new ArrayList<RosterExchangeListener>(); ++ ++ private Connection con; ++ ++ private PacketFilter packetFilter = new PacketExtensionFilter("x", "jabber:x:roster"); ++ private PacketListener packetListener; ++ ++ /** ++ * Creates a new roster exchange manager. ++ * ++ * @param con a Connection which is used to send and receive messages. ++ */ ++ public RosterExchangeManager(Connection con) { ++ this.con = con; ++ init(); ++ } ++ ++ /** ++ * Adds a listener to roster exchanges. The listener will be fired anytime roster entries ++ * are received from remote XMPP clients. ++ * ++ * @param rosterExchangeListener a roster exchange listener. ++ */ ++ public void addRosterListener(RosterExchangeListener rosterExchangeListener) { ++ synchronized (rosterExchangeListeners) { ++ if (!rosterExchangeListeners.contains(rosterExchangeListener)) { ++ rosterExchangeListeners.add(rosterExchangeListener); ++ } ++ } ++ } ++ ++ /** ++ * Removes a listener from roster exchanges. The listener will be fired anytime roster ++ * entries are received from remote XMPP clients. ++ * ++ * @param rosterExchangeListener a roster exchange listener.. ++ */ ++ public void removeRosterListener(RosterExchangeListener rosterExchangeListener) { ++ synchronized (rosterExchangeListeners) { ++ rosterExchangeListeners.remove(rosterExchangeListener); ++ } ++ } ++ ++ /** ++ * Sends a roster to userID. All the entries of the roster will be sent to the ++ * target user. ++ * ++ * @param roster the roster to send ++ * @param targetUserID the user that will receive the roster entries ++ */ ++ public void send(Roster roster, String targetUserID) { ++ // Create a new message to send the roster ++ Message msg = new Message(targetUserID); ++ // Create a RosterExchange Package and add it to the message ++ RosterExchange rosterExchange = new RosterExchange(roster); ++ msg.addExtension(rosterExchange); ++ ++ // Send the message that contains the roster ++ con.sendPacket(msg); ++ } ++ ++ /** ++ * Sends a roster entry to userID. ++ * ++ * @param rosterEntry the roster entry to send ++ * @param targetUserID the user that will receive the roster entries ++ */ ++ public void send(RosterEntry rosterEntry, String targetUserID) { ++ // Create a new message to send the roster ++ Message msg = new Message(targetUserID); ++ // Create a RosterExchange Package and add it to the message ++ RosterExchange rosterExchange = new RosterExchange(); ++ rosterExchange.addRosterEntry(rosterEntry); ++ msg.addExtension(rosterExchange); ++ ++ // Send the message that contains the roster ++ con.sendPacket(msg); ++ } ++ ++ /** ++ * Sends a roster group to userID. All the entries of the group will be sent to the ++ * target user. ++ * ++ * @param rosterGroup the roster group to send ++ * @param targetUserID the user that will receive the roster entries ++ */ ++ public void send(RosterGroup rosterGroup, String targetUserID) { ++ // Create a new message to send the roster ++ Message msg = new Message(targetUserID); ++ // Create a RosterExchange Package and add it to the message ++ RosterExchange rosterExchange = new RosterExchange(); ++ for (RosterEntry entry : rosterGroup.getEntries()) { ++ rosterExchange.addRosterEntry(entry); ++ } ++ msg.addExtension(rosterExchange); ++ ++ // Send the message that contains the roster ++ con.sendPacket(msg); ++ } ++ ++ /** ++ * Fires roster exchange listeners. ++ */ ++ private void fireRosterExchangeListeners(String from, Iterator remoteRosterEntries) { ++ RosterExchangeListener[] listeners = null; ++ synchronized (rosterExchangeListeners) { ++ listeners = new RosterExchangeListener[rosterExchangeListeners.size()]; ++ rosterExchangeListeners.toArray(listeners); ++ } ++ for (int i = 0; i < listeners.length; i++) { ++ listeners[i].entriesReceived(from, remoteRosterEntries); ++ } ++ } ++ ++ private void init() { ++ // Listens for all roster exchange packets and fire the roster exchange listeners. ++ packetListener = new PacketListener() { ++ public void processPacket(Packet packet) { ++ Message message = (Message) packet; ++ RosterExchange rosterExchange = ++ (RosterExchange) message.getExtension("x", "jabber:x:roster"); ++ // Fire event for roster exchange listeners ++ fireRosterExchangeListeners(message.getFrom(), rosterExchange.getRosterEntries()); ++ }; ++ ++ }; ++ con.addPacketListener(packetListener, packetFilter); ++ } ++ ++ public void destroy() { ++ if (con != null) ++ con.removePacketListener(packetListener); ++ ++ } ++ protected void finalize() throws Throwable { ++ destroy(); ++ super.finalize(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/ServiceDiscoveryManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ServiceDiscoveryManager.java +new file mode 100644 +index 0000000..fc9cace +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ServiceDiscoveryManager.java +@@ -0,0 +1,789 @@ ++/** ++ * $RCSfile$ ++ * $Revision: 9920 $ ++ * $Date: 2008-02-16 00:32:46 +0800 (Sat, 16 Feb 2008) $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import org.jivesoftware.smack.*; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.Presence; ++import org.jivesoftware.smack.packet.XMPPError; ++import org.jivesoftware.smackx.entitycaps.CapsVerListener; ++import org.jivesoftware.smackx.entitycaps.EntityCapsManager; ++import org.jivesoftware.smackx.packet.CapsExtension; ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++import org.jivesoftware.smackx.packet.DiscoverItems; ++import org.jivesoftware.smackx.packet.DataForm; ++ ++import java.util.*; ++import java.util.concurrent.ConcurrentHashMap; ++ ++/** ++ * Manages discovery of services in XMPP entities. This class provides: ++ * <ol> ++ * <li>A registry of supported features in this XMPP entity. ++ * <li>Automatic response when this XMPP entity is queried for information. ++ * <li>Ability to discover items and information of remote XMPP entities. ++ * <li>Ability to publish publicly available items. ++ * </ol> ++ * ++ * @author Gaston Dombiak ++ */ ++public class ServiceDiscoveryManager implements ServiceDiscoveryManagerInterface { ++ ++ private static String identityName = "Smack"; ++ private static String identityType = "pc"; ++ ++ private static boolean cacheNonCaps=true; ++ ++ private boolean sendPresence = false; ++ ++ private Map<String,DiscoverInfo> nonCapsCache = ++ new ConcurrentHashMap<String,DiscoverInfo>(); ++ ++ private EntityCapsManager capsManager; ++ ++ private static Map<Connection, ServiceDiscoveryManager> instances = ++ new ConcurrentHashMap<Connection, ServiceDiscoveryManager>(); ++ ++ private Connection connection; ++ private final List<String> features = new ArrayList<String>(); ++ private DataForm extendedInfo = null; ++ private Map<String, NodeInformationProvider> nodeInformationProviders = ++ new ConcurrentHashMap<String, NodeInformationProvider>(); ++ ++ // Create a new ServiceDiscoveryManager on every established connection ++ static { ++ // Add service discovery for normal XMPP c2s connections ++ XMPPConnection.addConnectionCreationListener(new ConnectionCreationListener() { ++ public void connectionCreated(Connection connection) { ++ new ServiceDiscoveryManager(connection); ++ } ++ }); ++ } ++ ++ /** ++ * Creates a new ServiceDiscoveryManager for a given connection. This means that the ++ * service manager will respond to any service discovery request that the connection may ++ * receive. ++ * ++ * @param connection the connection to which a ServiceDiscoveryManager is going to be created. ++ */ ++ public ServiceDiscoveryManager(Connection connection) { ++ this.connection = connection; ++ ++ // For every XMPPConnection, add one EntityCapsManager. ++ if (connection instanceof XMPPConnection ++ && ((XMPPConnection)connection).isEntityCapsEnabled()) { ++ setEntityCapsManager(new EntityCapsManager(this)); ++ capsManager.addCapsVerListener(new CapsPresenceRenewer()); ++ } ++ ++ renewEntityCapsVersion(); ++ ++ init(); ++ } ++ ++ /** ++ * Returns the ServiceDiscoveryManager instance associated with a given Connection. ++ * ++ * @param connection the connection used to look for the proper ServiceDiscoveryManager. ++ * @return the ServiceDiscoveryManager associated with a given Connection. ++ */ ++ public static ServiceDiscoveryManager getInstanceFor(Connection connection) { ++ return instances.get(connection); ++ } ++ ++ /** ++ * Returns the name of the client that will be returned when asked for the client identity ++ * in a disco request. The name could be any value you need to identity this client. ++ * ++ * @return the name of the client that will be returned when asked for the client identity ++ * in a disco request. ++ */ ++ public static String getIdentityName() { ++ return identityName; ++ } ++ ++ /** ++ * Sets the name of the client that will be returned when asked for the client identity ++ * in a disco request. The name could be any value you need to identity this client. ++ * ++ * @param name the name of the client that will be returned when asked for the client identity ++ * in a disco request. ++ */ ++ public static void setIdentityName(String name) { ++ identityName = name; ++ } ++ ++ /** ++ * Returns the type of client that will be returned when asked for the client identity in a ++ * disco request. The valid types are defined by the category client. Follow this link to learn ++ * the possible types: <a href="http://xmpp.org/registrar/disco-categories.html#client">Jabber::Registrar</a>. ++ * ++ * @return the type of client that will be returned when asked for the client identity in a ++ * disco request. ++ */ ++ public static String getIdentityType() { ++ return identityType; ++ } ++ ++ /** ++ * Sets the type of client that will be returned when asked for the client identity in a ++ * disco request. The valid types are defined by the category client. Follow this link to learn ++ * the possible types: <a href="http://xmpp.org/registrar/disco-categories.html#client">Jabber::Registrar</a>. ++ * ++ * @param type the type of client that will be returned when asked for the client identity in a ++ * disco request. ++ */ ++ public static void setIdentityType(String type) { ++ identityType = type; ++ } ++ ++ /** ++ * Enables caching of non caps entities to reduce traffic. If enabled discover infos of ++ * entities without xep-0115 are store in a String,DiscoverInfo map and ++ * discoverInfo(String) queries this map before sending a real discover info to the ++ * remote entity. Enabled by default. ++ */ ++ public static void setNonCapsCaching(boolean set){ ++ cacheNonCaps = set; ++ } ++ ++ /** ++ * Check if caching of non caps entities is enabled ++ */ ++ ++ public static boolean isNonCapsCachingEnabled(){ ++ return cacheNonCaps; ++ } ++ ++ /** ++ * Add discover info response data. ++ * ++ * @param response the discover info response packet ++ */ ++ public void addDiscoverInfoTo(DiscoverInfo response) { ++ // Set this client identity ++ DiscoverInfo.Identity identity = new DiscoverInfo.Identity("client", ++ getIdentityName()); ++ identity.setType(getIdentityType()); ++ response.addIdentity(identity); ++ // Add the registered features to the response ++ synchronized (features) { ++ for (Iterator<String> it = getFeatures(); it.hasNext();) { ++ response.addFeature(it.next()); ++ } ++ if (extendedInfo != null) { ++ response.addExtension(extendedInfo); ++ } ++ } ++ } ++ ++ /** ++ * Get a DiscoverInfo for the current entity caps node. ++ * ++ * @return a DiscoverInfo for the current entity caps node ++ */ ++ public DiscoverInfo getOwnDiscoverInfo() { ++ DiscoverInfo di = new DiscoverInfo(); ++ di.setType(IQ.Type.RESULT); ++ di.setNode(capsManager.getNode() + "#" + getEntityCapsVersion()); ++ ++ // Add discover info ++ addDiscoverInfoTo(di); ++ ++ return di; ++ } ++ ++ /** ++ * Initializes the packet listeners of the connection that will answer to any ++ * service discovery request. ++ */ ++ private void init() { ++ // Register the new instance and associate it with the connection ++ instances.put(connection, this); ++ // Add a listener to the connection that removes the registered instance when ++ // the connection is closed ++ connection.addConnectionListener(new ConnectionListener() { ++ public void connectionClosed() { ++ // Unregister this instance since the connection has been closed ++ instances.remove(connection); ++ } ++ ++ public void connectionClosedOnError(Exception e) { ++ // ignore ++ } ++ ++ public void reconnectionFailed(Exception e) { ++ // ignore ++ } ++ ++ public void reconnectingIn(int seconds) { ++ // ignore ++ } ++ ++ public void reconnectionSuccessful() { ++ // ignore ++ } ++ }); ++ ++ // Intercept presence packages and add caps data when intended. ++ // XEP-0115 specifies that a client SHOULD include entity capabilities ++ // with every presence notification it sends. ++ PacketFilter capsPacketFilter = new PacketTypeFilter(Presence.class); ++ PacketInterceptor packetInterceptor = new PacketInterceptor() { ++ public void interceptPacket(Packet packet) { ++ if (capsManager != null) { ++ String ver = getEntityCapsVersion(); ++ CapsExtension caps = new CapsExtension(capsManager.getNode(), ver, "sha-1"); ++ packet.addExtension(caps); ++ } ++ } ++ }; ++ connection.addPacketInterceptor(packetInterceptor, capsPacketFilter); ++ ++ // Listen for disco#items requests and answer with an empty result ++ PacketFilter packetFilter = new PacketTypeFilter(DiscoverItems.class); ++ PacketListener packetListener = new PacketListener() { ++ public void processPacket(Packet packet) { ++ DiscoverItems discoverItems = (DiscoverItems) packet; ++ // Send back the items defined in the client if the request is of type GET ++ if (discoverItems != null && discoverItems.getType() == IQ.Type.GET) { ++ DiscoverItems response = new DiscoverItems(); ++ response.setType(IQ.Type.RESULT); ++ response.setTo(discoverItems.getFrom()); ++ response.setPacketID(discoverItems.getPacketID()); ++ response.setNode(discoverItems.getNode()); ++ ++ // Add the defined items related to the requested node. Look for ++ // the NodeInformationProvider associated with the requested node. ++ NodeInformationProvider nodeInformationProvider = ++ getNodeInformationProvider(discoverItems.getNode()); ++ if (nodeInformationProvider != null) { ++ // Specified node was found ++ List<DiscoverItems.Item> items = nodeInformationProvider.getNodeItems(); ++ if (items != null) { ++ for (DiscoverItems.Item item : items) { ++ response.addItem(item); ++ } ++ } ++ } else if(discoverItems.getNode() != null) { ++ // Return <item-not-found/> error since client doesn't contain ++ // the specified node ++ response.setType(IQ.Type.ERROR); ++ response.setError(new XMPPError(XMPPError.Condition.item_not_found)); ++ } ++ connection.sendPacket(response); ++ } ++ } ++ }; ++ connection.addPacketListener(packetListener, packetFilter); ++ ++ // Listen for disco#info requests and answer the client's supported features ++ // To add a new feature as supported use the #addFeature message ++ packetFilter = new PacketTypeFilter(DiscoverInfo.class); ++ packetListener = new PacketListener() { ++ public void processPacket(Packet packet) { ++ DiscoverInfo discoverInfo = (DiscoverInfo) packet; ++ // Answer the client's supported features if the request is of the GET type ++ if (discoverInfo != null && discoverInfo.getType() == IQ.Type.GET) { ++ DiscoverInfo response = new DiscoverInfo(); ++ response.setType(IQ.Type.RESULT); ++ response.setTo(discoverInfo.getFrom()); ++ response.setPacketID(discoverInfo.getPacketID()); ++ response.setNode(discoverInfo.getNode()); ++ // Add the client's identity and features if "node" is ++ // null or our entity caps version. ++ if (discoverInfo.getNode() == null || ++ capsManager == null || ++ (capsManager.getNode() + "#" + ++ getEntityCapsVersion()).equals(discoverInfo.getNode())) { ++ addDiscoverInfoTo(response); ++ } ++ else { ++ // Disco#info was sent to a node. Check if we have information of the ++ // specified node ++ NodeInformationProvider nodeInformationProvider = ++ getNodeInformationProvider(discoverInfo.getNode()); ++ if (nodeInformationProvider != null) { ++ // Node was found. Add node features ++ List<String> features = nodeInformationProvider.getNodeFeatures(); ++ if (features != null) { ++ for(String feature : features) { ++ response.addFeature(feature); ++ } ++ } ++ // Add node identities ++ List<DiscoverInfo.Identity> identities = ++ nodeInformationProvider.getNodeIdentities(); ++ if (identities != null) { ++ for (DiscoverInfo.Identity identity : identities) { ++ response.addIdentity(identity); ++ } ++ } ++ } ++ else { ++ // Return <item-not-found/> error since specified node was not found ++ response.setType(IQ.Type.ERROR); ++ response.setError(new XMPPError(XMPPError.Condition.item_not_found)); ++ } ++ } ++ connection.sendPacket(response); ++ } ++ } ++ }; ++ connection.addPacketListener(packetListener, packetFilter); ++ } ++ ++ /** ++ * Returns the NodeInformationProvider responsible for providing information ++ * (ie items) related to a given node or <tt>null</null> if none.<p> ++ * ++ * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the ++ * NodeInformationProvider will provide information about the rooms where the user has joined. ++ * ++ * @param node the node that contains items associated with an entity not addressable as a JID. ++ * @return the NodeInformationProvider responsible for providing information related ++ * to a given node. ++ */ ++ private NodeInformationProvider getNodeInformationProvider(String node) { ++ if (node == null) { ++ return null; ++ } ++ return nodeInformationProviders.get(node); ++ } ++ ++ /** ++ * Sets the NodeInformationProvider responsible for providing information ++ * (ie items) related to a given node. Every time this client receives a disco request ++ * regarding the items of a given node, the provider associated to that node will be the ++ * responsible for providing the requested information.<p> ++ * ++ * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the ++ * NodeInformationProvider will provide information about the rooms where the user has joined. ++ * ++ * @param node the node whose items will be provided by the NodeInformationProvider. ++ * @param listener the NodeInformationProvider responsible for providing items related ++ * to the node. ++ */ ++ public void setNodeInformationProvider(String node, NodeInformationProvider listener) { ++ nodeInformationProviders.put(node, listener); ++ } ++ ++ /** ++ * Removes the NodeInformationProvider responsible for providing information ++ * (ie items) related to a given node. This means that no more information will be ++ * available for the specified node. ++ * ++ * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the ++ * NodeInformationProvider will provide information about the rooms where the user has joined. ++ * ++ * @param node the node to remove the associated NodeInformationProvider. ++ */ ++ public void removeNodeInformationProvider(String node) { ++ nodeInformationProviders.remove(node); ++ } ++ ++ /** ++ * Returns the supported features by this XMPP entity. ++ * ++ * @return an Iterator on the supported features by this XMPP entity. ++ */ ++ public Iterator<String> getFeatures() { ++ synchronized (features) { ++ return Collections.unmodifiableList(new ArrayList<String>(features)).iterator(); ++ } ++ } ++ ++ /** ++ * Registers that a new feature is supported by this XMPP entity. When this client is ++ * queried for its information the registered features will be answered.<p> ++ * ++ * Since no packet is actually sent to the server it is safe to perform this operation ++ * before logging to the server. In fact, you may want to configure the supported features ++ * before logging to the server so that the information is already available if it is required ++ * upon login. ++ * ++ * @param feature the feature to register as supported. ++ */ ++ public void addFeature(String feature) { ++ synchronized (features) { ++ features.add(feature); ++ renewEntityCapsVersion(); ++ } ++ } ++ ++ /** ++ * Removes the specified feature from the supported features by this XMPP entity.<p> ++ * ++ * Since no packet is actually sent to the server it is safe to perform this operation ++ * before logging to the server. ++ * ++ * @param feature the feature to remove from the supported features. ++ */ ++ public void removeFeature(String feature) { ++ synchronized (features) { ++ features.remove(feature); ++ renewEntityCapsVersion(); ++ } ++ } ++ ++ /** ++ * Returns true if the specified feature is registered in the ServiceDiscoveryManager. ++ * ++ * @param feature the feature to look for. ++ * @return a boolean indicating if the specified featured is registered or not. ++ */ ++ public boolean includesFeature(String feature) { ++ synchronized (features) { ++ return features.contains(feature); ++ } ++ } ++ ++ /** ++ * Registers extended discovery information of this XMPP entity. When this ++ * client is queried for its information this data form will be returned as ++ * specified by XEP-0128. ++ * <p> ++ * ++ * Since no packet is actually sent to the server it is safe to perform this ++ * operation before logging to the server. In fact, you may want to ++ * configure the extended info before logging to the server so that the ++ * information is already available if it is required upon login. ++ * ++ * @param info ++ * the data form that contains the extend service discovery ++ * information. ++ */ ++ public void setExtendedInfo(DataForm info) { ++ extendedInfo = info; ++ renewEntityCapsVersion(); ++ } ++ ++ /** ++ * Removes the dataform containing extended service discovery information ++ * from the information returned by this XMPP entity.<p> ++ * ++ * Since no packet is actually sent to the server it is safe to perform this ++ * operation before logging to the server. ++ */ ++ public void removeExtendedInfo() { ++ extendedInfo = null; ++ renewEntityCapsVersion(); ++ } ++ ++ /** ++ * Returns the discovered information of a given XMPP entity addressed by its JID ++ * if it's known by the entity caps manager. ++ * ++ * @param entityID the address of the XMPP entity ++ * @return the disovered info or null if no such info is available from the ++ * entity caps manager. ++ * @throws XMPPException if the operation failed for some reason. ++ */ ++ public DiscoverInfo discoverInfoByCaps(String entityID) throws XMPPException { ++ DiscoverInfo info = capsManager.getDiscoverInfoByUser(entityID); ++ ++ if (info != null) { ++ DiscoverInfo newInfo = cloneDiscoverInfo(info); ++ newInfo.setFrom(entityID); ++ return newInfo; ++ } ++ else { ++ return null; ++ } ++ } ++ ++ /** ++ * Returns the discovered information of a given XMPP entity addressed by its JID. ++ * ++ * @param entityID the address of the XMPP entity. ++ * @return the discovered information. ++ * @throws XMPPException if the operation failed for some reason. ++ */ ++ public DiscoverInfo discoverInfo(String entityID) throws XMPPException { ++ // Check if the have it cached in the Entity Capabilities Manager ++ DiscoverInfo info = discoverInfoByCaps(entityID); ++ ++ if (info != null) { ++ return info; ++ } else { ++ // If the caps node is known, use it in the request. ++ String node = null; ++ ++ if (capsManager != null) { ++ // Get the newest node#version ++ node = capsManager.getNodeVersionByUser(entityID); ++ } ++ ++ // Check if we cached DiscoverInfo for nonCaps entity ++ if (cacheNonCaps ++ && node == null ++ && nonCapsCache.containsKey(entityID)) { ++ return nonCapsCache.get(entityID); ++ } ++ // Discover by requesting from the remote client ++ info = discoverInfo(entityID, node); ++ ++ // If the node version is known, store the new entry. ++ if (node != null && capsManager != null) { ++ EntityCapsManager.addDiscoverInfoByNode(node, info); ++ } ++ // If this is a non caps entity store the discover in nonCapsCache map ++ else if (cacheNonCaps && node == null) { ++ nonCapsCache.put(entityID, info); ++ } ++ return info; ++ } ++ } ++ ++ /** ++ * Returns the discovered information of a given XMPP entity addressed by its JID and ++ * note attribute. Use this message only when trying to query information which is not ++ * directly addressable. ++ * ++ * @param entityID the address of the XMPP entity. ++ * @param node the attribute that supplements the 'jid' attribute. ++ * @return the discovered information. ++ * @throws XMPPException if the operation failed for some reason. ++ */ ++ public DiscoverInfo discoverInfo(String entityID, String node) throws XMPPException { ++ // Discover the entity's info ++ DiscoverInfo disco = new DiscoverInfo(); ++ disco.setType(IQ.Type.GET); ++ disco.setTo(entityID); ++ disco.setNode(node); ++ ++ // Create a packet collector to listen for a response. ++ PacketCollector collector = ++ connection.createPacketCollector(new PacketIDFilter(disco.getPacketID())); ++ ++ connection.sendPacket(disco); ++ ++ // Wait up to 5 seconds for a result. ++ IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ collector.cancel(); ++ if (result == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ if (result.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(result.getError()); ++ } ++ return (DiscoverInfo) result; ++ } ++ ++ /** ++ * Returns the discovered items of a given XMPP entity addressed by its JID. ++ * ++ * @param entityID the address of the XMPP entity. ++ * @return the discovered information. ++ * @throws XMPPException if the operation failed for some reason. ++ */ ++ public DiscoverItems discoverItems(String entityID) throws XMPPException { ++ return discoverItems(entityID, null); ++ } ++ ++ /** ++ * Returns the discovered items of a given XMPP entity addressed by its JID and ++ * note attribute. Use this message only when trying to query information which is not ++ * directly addressable. ++ * ++ * @param entityID the address of the XMPP entity. ++ * @param node the attribute that supplements the 'jid' attribute. ++ * @return the discovered items. ++ * @throws XMPPException if the operation failed for some reason. ++ */ ++ public DiscoverItems discoverItems(String entityID, String node) throws XMPPException { ++ // Discover the entity's items ++ DiscoverItems disco = new DiscoverItems(); ++ disco.setType(IQ.Type.GET); ++ disco.setTo(entityID); ++ disco.setNode(node); ++ ++ // Create a packet collector to listen for a response. ++ PacketCollector collector = ++ connection.createPacketCollector(new PacketIDFilter(disco.getPacketID())); ++ ++ connection.sendPacket(disco); ++ ++ // Wait up to 5 seconds for a result. ++ IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ collector.cancel(); ++ if (result == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ if (result.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(result.getError()); ++ } ++ return (DiscoverItems) result; ++ } ++ ++ /** ++ * Returns true if the server supports publishing of items. A client may wish to publish items ++ * to the server so that the server can provide items associated to the client. These items will ++ * be returned by the server whenever the server receives a disco request targeted to the bare ++ * address of the client (i.e. user@host.com). ++ * ++ * @param entityID the address of the XMPP entity. ++ * @return true if the server supports publishing of items. ++ * @throws XMPPException if the operation failed for some reason. ++ */ ++ public boolean canPublishItems(String entityID) throws XMPPException { ++ DiscoverInfo info = discoverInfo(entityID); ++ return canPublishItems(info); ++ } ++ ++ /** ++ * Returns true if the server supports publishing of items. A client may wish to publish items ++ * to the server so that the server can provide items associated to the client. These items will ++ * be returned by the server whenever the server receives a disco request targeted to the bare ++ * address of the client (i.e. user@host.com). ++ * ++ * @param DiscoverInfo the discover info packet to check. ++ * @return true if the server supports publishing of items. ++ */ ++ public static boolean canPublishItems(DiscoverInfo info) { ++ return info.containsFeature("http://jabber.org/protocol/disco#publish"); ++ } ++ ++ /** ++ * Publishes new items to a parent entity. The item elements to publish MUST have at least ++ * a 'jid' attribute specifying the Entity ID of the item, and an action attribute which ++ * specifies the action being taken for that item. Possible action values are: "update" and ++ * "remove". ++ * ++ * @param entityID the address of the XMPP entity. ++ * @param discoverItems the DiscoveryItems to publish. ++ * @throws XMPPException if the operation failed for some reason. ++ */ ++ public void publishItems(String entityID, DiscoverItems discoverItems) ++ throws XMPPException { ++ publishItems(entityID, null, discoverItems); ++ } ++ ++ /** ++ * Publishes new items to a parent entity and node. The item elements to publish MUST have at ++ * least a 'jid' attribute specifying the Entity ID of the item, and an action attribute which ++ * specifies the action being taken for that item. Possible action values are: "update" and ++ * "remove". ++ * ++ * @param entityID the address of the XMPP entity. ++ * @param node the attribute that supplements the 'jid' attribute. ++ * @param discoverItems the DiscoveryItems to publish. ++ * @throws XMPPException if the operation failed for some reason. ++ */ ++ public void publishItems(String entityID, String node, DiscoverItems discoverItems) ++ throws XMPPException { ++ discoverItems.setType(IQ.Type.SET); ++ discoverItems.setTo(entityID); ++ discoverItems.setNode(node); ++ ++ // Create a packet collector to listen for a response. ++ PacketCollector collector = ++ connection.createPacketCollector(new PacketIDFilter(discoverItems.getPacketID())); ++ ++ connection.sendPacket(discoverItems); ++ ++ // Wait up to 5 seconds for a result. ++ IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ collector.cancel(); ++ if (result == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ if (result.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(result.getError()); ++ } ++ } ++ ++ private DiscoverInfo cloneDiscoverInfo(DiscoverInfo disco) { ++ return disco.clone(); ++ } ++ ++ /** ++ * Entity Capabilities ++ */ ++ ++ public void setEntityCapsManager(EntityCapsManager manager) { ++ capsManager = manager; ++ if (connection.getCapsNode() != null ++ && connection.getHost() != null) { ++ capsManager.addUserCapsNode(connection.getHost(), connection.getCapsNode()); ++ } ++ capsManager.addPacketListener(connection); ++ } ++ ++ /** ++ * Updates the Entity Capabilities Verification String ++ * if EntityCaps is enabled ++ */ ++ private void renewEntityCapsVersion() { ++ // If a XMPPConnection is the managed one, see that the new ++ // version is updated ++ if (connection instanceof XMPPConnection) { ++ if (capsManager != null) { ++ capsManager.calculateEntityCapsVersion(getOwnDiscoverInfo(), ++ identityType, identityName, extendedInfo); ++ //capsManager.notifyCapsVerListeners(); ++ } ++ } ++ } ++ ++ private String getEntityCapsVersion() { ++ if (capsManager != null) { ++ return capsManager.getCapsVersion(); ++ } ++ else { ++ return null; ++ } ++ } ++ ++ public EntityCapsManager getEntityCapsManager(){ ++ return capsManager; ++ } ++ ++ private void setSendPresence() { ++ sendPresence = true; ++ } ++ ++ private boolean isSendPresence() { ++ return sendPresence; ++ } ++ ++ ++ private class CapsPresenceRenewer implements CapsVerListener { ++ public void capsVerUpdated(String ver) { ++ // Send an empty presence, and let the packet interceptor ++ // add a <c/> node to it. ++ if (((Connection)connection).isAuthenticated() && ++ (((Connection)connection).isSendPresence() || ++ isSendPresence())) { ++ Presence presence = new Presence(Presence.Type.available); ++ connection.sendPacket(presence); ++ } ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/ServiceDiscoveryManagerInterface.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ServiceDiscoveryManagerInterface.java +new file mode 100644 +index 0000000..a59381a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ServiceDiscoveryManagerInterface.java +@@ -0,0 +1,5 @@ ++package org.jivesoftware.smackx; ++ ++public interface ServiceDiscoveryManagerInterface { ++ public void addFeature(String feature); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/SharedGroupManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/SharedGroupManager.java +new file mode 100644 +index 0000000..c973730 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/SharedGroupManager.java +@@ -0,0 +1,53 @@ ++package org.jivesoftware.smackx; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.PacketCollector; ++import org.jivesoftware.smack.SmackConfiguration; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smackx.packet.SharedGroupsInfo; ++ ++import java.util.List; ++ ++/** ++ * A SharedGroupManager provides services for discovering the shared groups where a user belongs.<p> ++ * ++ * Important note: This functionality is not part of the XMPP spec and it will only work ++ * with Wildfire. ++ * ++ * @author Gaston Dombiak ++ */ ++public class SharedGroupManager { ++ ++ /** ++ * Returns the collection that will contain the name of the shared groups where the user ++ * logged in with the specified session belongs. ++ * ++ * @param connection connection to use to get the user's shared groups. ++ * @return collection with the shared groups' name of the logged user. ++ */ ++ public static List getSharedGroups(Connection connection) throws XMPPException { ++ // Discover the shared groups of the logged user ++ SharedGroupsInfo info = new SharedGroupsInfo(); ++ info.setType(IQ.Type.GET); ++ ++ // Create a packet collector to listen for a response. ++ PacketCollector collector = ++ connection.createPacketCollector(new PacketIDFilter(info.getPacketID())); ++ ++ connection.sendPacket(info); ++ ++ // Wait up to 5 seconds for a result. ++ IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ collector.cancel(); ++ if (result == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ if (result.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(result.getError()); ++ } ++ return ((SharedGroupsInfo) result).getGroups(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/XHTMLManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/XHTMLManager.java +new file mode 100644 +index 0000000..f0c102c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/XHTMLManager.java +@@ -0,0 +1,144 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import org.jivesoftware.smack.ConnectionCreationListener; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.packet.Message; ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++import org.jivesoftware.smackx.packet.XHTMLExtension; ++ ++import java.util.Iterator; ++ ++/** ++ * Manages XHTML formatted texts within messages. A XHTMLManager provides a high level access to ++ * get and set XHTML bodies to messages, enable and disable XHTML support and check if remote XMPP ++ * clients support XHTML. ++ * ++ * @author Gaston Dombiak ++ */ ++public class XHTMLManager { ++ ++ private final static String namespace = "http://jabber.org/protocol/xhtml-im"; ++ ++ // Enable the XHTML support on every established connection ++ // The ServiceDiscoveryManager class should have been already initialized ++ static { ++ Connection.addConnectionCreationListener(new ConnectionCreationListener() { ++ public void connectionCreated(Connection connection) { ++ XHTMLManager.setServiceEnabled(connection, true); ++ } ++ }); ++ } ++ ++ /** ++ * Returns an Iterator for the XHTML bodies in the message. Returns null if ++ * the message does not contain an XHTML extension. ++ * ++ * @param message an XHTML message ++ * @return an Iterator for the bodies in the message or null if none. ++ */ ++ public static Iterator getBodies(Message message) { ++ XHTMLExtension xhtmlExtension = (XHTMLExtension) message.getExtension("html", namespace); ++ if (xhtmlExtension != null) ++ return xhtmlExtension.getBodies(); ++ else ++ return null; ++ } ++ ++ /** ++ * Adds an XHTML body to the message. ++ * ++ * @param message the message that will receive the XHTML body ++ * @param body the string to add as an XHTML body to the message ++ */ ++ public static void addBody(Message message, String body) { ++ XHTMLExtension xhtmlExtension = (XHTMLExtension) message.getExtension("html", namespace); ++ if (xhtmlExtension == null) { ++ // Create an XHTMLExtension and add it to the message ++ xhtmlExtension = new XHTMLExtension(); ++ message.addExtension(xhtmlExtension); ++ } ++ // Add the required bodies to the message ++ xhtmlExtension.addBody(body); ++ } ++ ++ /** ++ * Returns true if the message contains an XHTML extension. ++ * ++ * @param message the message to check if contains an XHTML extentsion or not ++ * @return a boolean indicating whether the message is an XHTML message ++ */ ++ public static boolean isXHTMLMessage(Message message) { ++ return message.getExtension("html", namespace) != null; ++ } ++ ++ /** ++ * Enables or disables the XHTML support on a given connection.<p> ++ * ++ * Before starting to send XHTML messages to a user, check that the user can handle XHTML ++ * messages. Enable the XHTML support to indicate that this client handles XHTML messages. ++ * ++ * @param connection the connection where the service will be enabled or disabled ++ * @param enabled indicates if the service will be enabled or disabled ++ */ ++ public synchronized static void setServiceEnabled(Connection connection, boolean enabled) { ++ if (isServiceEnabled(connection) == enabled) ++ return; ++ ++ if (enabled) { ++ ServiceDiscoveryManager.getInstanceFor(connection).addFeature(namespace); ++ } ++ else { ++ ServiceDiscoveryManager.getInstanceFor(connection).removeFeature(namespace); ++ } ++ } ++ ++ /** ++ * Returns true if the XHTML support is enabled for the given connection. ++ * ++ * @param connection the connection to look for XHTML support ++ * @return a boolean indicating if the XHTML support is enabled for the given connection ++ */ ++ public static boolean isServiceEnabled(Connection connection) { ++ return ServiceDiscoveryManager.getInstanceFor(connection).includesFeature(namespace); ++ } ++ ++ /** ++ * Returns true if the specified user handles XHTML messages. ++ * ++ * @param connection the connection to use to perform the service discovery ++ * @param userID the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com ++ * @return a boolean indicating whether the specified user handles XHTML messages ++ */ ++ public static boolean isServiceEnabled(Connection connection, String userID) { ++ try { ++ DiscoverInfo result = ++ ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(userID); ++ return result.containsFeature(namespace); ++ } ++ catch (XMPPException e) { ++ e.printStackTrace(); ++ return false; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/XHTMLText.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/XHTMLText.java +new file mode 100644 +index 0000000..201e530 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/XHTMLText.java +@@ -0,0 +1,429 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx; ++ ++import org.jivesoftware.smack.util.StringUtils; ++ ++/** ++ * An XHTMLText represents formatted text. This class also helps to build valid ++ * XHTML tags. ++ * ++ * @author Gaston Dombiak ++ */ ++public class XHTMLText { ++ ++ private StringBuilder text = new StringBuilder(30); ++ ++ /** ++ * Creates a new XHTMLText with body tag params. ++ * ++ * @param style the XHTML style of the body ++ * @param lang the language of the body ++ */ ++ public XHTMLText(String style, String lang) { ++ appendOpenBodyTag(style, lang); ++ } ++ ++ /** ++ * Appends a tag that indicates that an anchor section begins. ++ * ++ * @param href indicates the URL being linked to ++ * @param style the XHTML style of the anchor ++ */ ++ public void appendOpenAnchorTag(String href, String style) { ++ StringBuilder sb = new StringBuilder("<a"); ++ if (href != null) { ++ sb.append(" href=\""); ++ sb.append(href); ++ sb.append("\""); ++ } ++ if (style != null) { ++ sb.append(" style=\""); ++ sb.append(style); ++ sb.append("\""); ++ } ++ sb.append(">"); ++ text.append(sb.toString()); ++ } ++ ++ /** ++ * Appends a tag that indicates that an anchor section ends. ++ * ++ */ ++ public void appendCloseAnchorTag() { ++ text.append("</a>"); ++ } ++ ++ /** ++ * Appends a tag that indicates that a blockquote section begins. ++ * ++ * @param style the XHTML style of the blockquote ++ */ ++ public void appendOpenBlockQuoteTag(String style) { ++ StringBuilder sb = new StringBuilder("<blockquote"); ++ if (style != null) { ++ sb.append(" style=\""); ++ sb.append(style); ++ sb.append("\""); ++ } ++ sb.append(">"); ++ text.append(sb.toString()); ++ } ++ ++ /** ++ * Appends a tag that indicates that a blockquote section ends. ++ * ++ */ ++ public void appendCloseBlockQuoteTag() { ++ text.append("</blockquote>"); ++ } ++ ++ /** ++ * Appends a tag that indicates that a body section begins. ++ * ++ * @param style the XHTML style of the body ++ * @param lang the language of the body ++ */ ++ private void appendOpenBodyTag(String style, String lang) { ++ StringBuilder sb = new StringBuilder("<body"); ++ if (style != null) { ++ sb.append(" style=\""); ++ sb.append(style); ++ sb.append("\""); ++ } ++ if (lang != null) { ++ sb.append(" xml:lang=\""); ++ sb.append(lang); ++ sb.append("\""); ++ } ++ sb.append(">"); ++ text.append(sb.toString()); ++ } ++ ++ /** ++ * Appends a tag that indicates that a body section ends. ++ * ++ */ ++ private String closeBodyTag() { ++ return "</body>"; ++ } ++ ++ /** ++ * Appends a tag that inserts a single carriage return. ++ * ++ */ ++ public void appendBrTag() { ++ text.append("<br/>"); ++ } ++ ++ /** ++ * Appends a tag that indicates a reference to work, such as a book, report or web site. ++ * ++ */ ++ public void appendOpenCiteTag() { ++ text.append("<cite>"); ++ } ++ ++ /** ++ * Appends a tag that indicates text that is the code for a program. ++ * ++ */ ++ public void appendOpenCodeTag() { ++ text.append("<code>"); ++ } ++ ++ /** ++ * Appends a tag that indicates end of text that is the code for a program. ++ * ++ */ ++ public void appendCloseCodeTag() { ++ text.append("</code>"); ++ } ++ ++ /** ++ * Appends a tag that indicates emphasis. ++ * ++ */ ++ public void appendOpenEmTag() { ++ text.append("<em>"); ++ } ++ ++ /** ++ * Appends a tag that indicates end of emphasis. ++ * ++ */ ++ public void appendCloseEmTag() { ++ text.append("</em>"); ++ } ++ ++ /** ++ * Appends a tag that indicates a header, a title of a section of the message. ++ * ++ * @param level the level of the Header. It should be a value between 1 and 3 ++ * @param style the XHTML style of the blockquote ++ */ ++ public void appendOpenHeaderTag(int level, String style) { ++ if (level > 3 || level < 1) { ++ return; ++ } ++ StringBuilder sb = new StringBuilder("<h"); ++ sb.append(level); ++ if (style != null) { ++ sb.append(" style=\""); ++ sb.append(style); ++ sb.append("\""); ++ } ++ sb.append(">"); ++ text.append(sb.toString()); ++ } ++ ++ /** ++ * Appends a tag that indicates that a header section ends. ++ * ++ * @param level the level of the Header. It should be a value between 1 and 3 ++ */ ++ public void appendCloseHeaderTag(int level) { ++ if (level > 3 || level < 1) { ++ return; ++ } ++ StringBuilder sb = new StringBuilder("</h"); ++ sb.append(level); ++ sb.append(">"); ++ text.append(sb.toString()); ++ } ++ ++ /** ++ * Appends a tag that indicates an image. ++ * ++ * @param align how text should flow around the picture ++ * @param alt the text to show if you don't show the picture ++ * @param height how tall is the picture ++ * @param src where to get the picture ++ * @param width how wide is the picture ++ */ ++ public void appendImageTag(String align, String alt, String height, String src, String width) { ++ StringBuilder sb = new StringBuilder("<img"); ++ if (align != null) { ++ sb.append(" align=\""); ++ sb.append(align); ++ sb.append("\""); ++ } ++ if (alt != null) { ++ sb.append(" alt=\""); ++ sb.append(alt); ++ sb.append("\""); ++ } ++ if (height != null) { ++ sb.append(" height=\""); ++ sb.append(height); ++ sb.append("\""); ++ } ++ if (src != null) { ++ sb.append(" src=\""); ++ sb.append(src); ++ sb.append("\""); ++ } ++ if (width != null) { ++ sb.append(" width=\""); ++ sb.append(width); ++ sb.append("\""); ++ } ++ sb.append(">"); ++ text.append(sb.toString()); ++ } ++ ++ /** ++ * Appends a tag that indicates the start of a new line item within a list. ++ * ++ * @param style the style of the line item ++ */ ++ public void appendLineItemTag(String style) { ++ StringBuilder sb = new StringBuilder("<li"); ++ if (style != null) { ++ sb.append(" style=\""); ++ sb.append(style); ++ sb.append("\""); ++ } ++ sb.append(">"); ++ text.append(sb.toString()); ++ } ++ ++ /** ++ * Appends a tag that creates an ordered list. "Ordered" means that the order of the items ++ * in the list is important. To show this, browsers automatically number the list. ++ * ++ * @param style the style of the ordered list ++ */ ++ public void appendOpenOrderedListTag(String style) { ++ StringBuilder sb = new StringBuilder("<ol"); ++ if (style != null) { ++ sb.append(" style=\""); ++ sb.append(style); ++ sb.append("\""); ++ } ++ sb.append(">"); ++ text.append(sb.toString()); ++ } ++ ++ /** ++ * Appends a tag that indicates that an ordered list section ends. ++ * ++ */ ++ public void appendCloseOrderedListTag() { ++ text.append("</ol>"); ++ } ++ ++ /** ++ * Appends a tag that creates an unordered list. The unordered part means that the items ++ * in the list are not in any particular order. ++ * ++ * @param style the style of the unordered list ++ */ ++ public void appendOpenUnorderedListTag(String style) { ++ StringBuilder sb = new StringBuilder("<ul"); ++ if (style != null) { ++ sb.append(" style=\""); ++ sb.append(style); ++ sb.append("\""); ++ } ++ sb.append(">"); ++ text.append(sb.toString()); ++ } ++ ++ /** ++ * Appends a tag that indicates that an unordered list section ends. ++ * ++ */ ++ public void appendCloseUnorderedListTag() { ++ text.append("</ul>"); ++ } ++ ++ /** ++ * Appends a tag that indicates the start of a new paragraph. This is usually rendered ++ * with two carriage returns, producing a single blank line in between the two paragraphs. ++ * ++ * @param style the style of the paragraph ++ */ ++ public void appendOpenParagraphTag(String style) { ++ StringBuilder sb = new StringBuilder("<p"); ++ if (style != null) { ++ sb.append(" style=\""); ++ sb.append(style); ++ sb.append("\""); ++ } ++ sb.append(">"); ++ text.append(sb.toString()); ++ } ++ ++ /** ++ * Appends a tag that indicates the end of a new paragraph. This is usually rendered ++ * with two carriage returns, producing a single blank line in between the two paragraphs. ++ * ++ */ ++ public void appendCloseParagraphTag() { ++ text.append("</p>"); ++ } ++ ++ /** ++ * Appends a tag that indicates that an inlined quote section begins. ++ * ++ * @param style the style of the inlined quote ++ */ ++ public void appendOpenInlinedQuoteTag(String style) { ++ StringBuilder sb = new StringBuilder("<q"); ++ if (style != null) { ++ sb.append(" style=\""); ++ sb.append(style); ++ sb.append("\""); ++ } ++ sb.append(">"); ++ text.append(sb.toString()); ++ } ++ ++ /** ++ * Appends a tag that indicates that an inlined quote section ends. ++ * ++ */ ++ public void appendCloseInlinedQuoteTag() { ++ text.append("</q>"); ++ } ++ ++ /** ++ * Appends a tag that allows to set the fonts for a span of text. ++ * ++ * @param style the style for a span of text ++ */ ++ public void appendOpenSpanTag(String style) { ++ StringBuilder sb = new StringBuilder("<span"); ++ if (style != null) { ++ sb.append(" style=\""); ++ sb.append(style); ++ sb.append("\""); ++ } ++ sb.append(">"); ++ text.append(sb.toString()); ++ } ++ ++ /** ++ * Appends a tag that indicates that a span section ends. ++ * ++ */ ++ public void appendCloseSpanTag() { ++ text.append("</span>"); ++ } ++ ++ /** ++ * Appends a tag that indicates text which should be more forceful than surrounding text. ++ * ++ */ ++ public void appendOpenStrongTag() { ++ text.append("<strong>"); ++ } ++ ++ /** ++ * Appends a tag that indicates that a strong section ends. ++ * ++ */ ++ public void appendCloseStrongTag() { ++ text.append("</strong>"); ++ } ++ ++ /** ++ * Appends a given text to the XHTMLText. ++ * ++ * @param textToAppend the text to append ++ */ ++ public void append(String textToAppend) { ++ text.append(StringUtils.escapeForXML(textToAppend)); ++ } ++ ++ /** ++ * Returns the text of the XHTMLText. ++ * ++ * Note: Automatically adds the closing body tag. ++ * ++ * @return the text of the XHTMLText ++ */ ++ public String toString() { ++ return text.toString().concat(closeBodyTag()); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/XmppStreamHandler.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/XmppStreamHandler.java +new file mode 100644 +index 0000000..1e836b1 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/XmppStreamHandler.java +@@ -0,0 +1,258 @@ ++/** ++ * Copyright 2012 Miron Cuperman. ++ * ++ * All rights reserved. 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.smackx; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++ ++import org.jivesoftware.smack.ConnectionListener; ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.XMPPConnection; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.packet.UnknownPacket; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.jivesoftware.smack.provider.ProviderManager; ++import org.jivesoftware.smack.util.StringUtils; ++import org.xmlpull.v1.XmlPullParser; ++ ++public class XmppStreamHandler { ++ private static final String URN_SM_2 = "urn:xmpp:sm:2"; ++ private XMPPConnection mConnection; ++ private boolean isSmAvailable = false; ++ private boolean isSmEnabled = false; ++ private long previousIncomingStanzaCount = -1; ++ private String sessionId; ++ private long incomingStanzaCount = 0; ++ private long outgoingStanzaCount = 0; ++ private List<ManagedPacket> sendList = null; ++ ++ public static class ManagedPacket { ++ Packet packet; ++ long stanzaCount; ++ } ++ ++ public XmppStreamHandler(XMPPConnection connection) { ++ mConnection = connection; ++ startListening(); ++ } ++ ++ public boolean isResumePossible() { ++ return sessionId != null; ++ } ++ ++ public static void addExtensionProviders() { ++ addSimplePacketExtension("sm", URN_SM_2); ++ addSimplePacketExtension("r", URN_SM_2); ++ addSimplePacketExtension("a", URN_SM_2); ++ addSimplePacketExtension("enabled", URN_SM_2); ++ addSimplePacketExtension("resumed", URN_SM_2); ++ addSimplePacketExtension("failed", URN_SM_2); ++ } ++ ++ public void notifyInitialLogin() { ++ if (isSmAvailable) { ++ sendEnablePacket(); ++ } ++ } ++ ++ private void sendEnablePacket() { ++ if (sessionId != null) { ++ // TODO binding ++ StreamHandlingPacket resumePacket = new StreamHandlingPacket("resume", URN_SM_2); ++ resumePacket.addAttribute("h", String.valueOf(previousIncomingStanzaCount)); ++ resumePacket.addAttribute("previd", sessionId); ++ mConnection.sendPacket(resumePacket); ++ } ++ else { ++ StreamHandlingPacket enablePacket = new StreamHandlingPacket("enable", URN_SM_2); ++ enablePacket.addAttribute("resume", "true"); ++ mConnection.sendPacket(enablePacket); ++ } ++ } ++ ++ private void startListening() { ++ mConnection.addConnectionListener(new ConnectionListener() { ++ public void reconnectionSuccessful() { ++ if (isSmAvailable) { ++ sendEnablePacket(); ++ } ++ else { ++ isSmEnabled = false; ++ sessionId = null; ++ } ++ } ++ ++ public void reconnectionFailed(Exception e) { ++ } ++ ++ public void reconnectingIn(int seconds) { ++ } ++ ++ public void connectionClosedOnError(Exception e) { ++ if (isSmEnabled && sessionId != null) { ++ previousIncomingStanzaCount = incomingStanzaCount; ++ } ++ isSmEnabled = false; ++ isSmAvailable = false; ++ } ++ ++ public void connectionClosed() { ++ previousIncomingStanzaCount = -1; ++ } ++ }); ++ ++ mConnection.addPacketSendingListener(new PacketListener() { ++ public void processPacket(Packet packet) { ++ if (isSmAvailable) { ++ // for packets sent directly after <enable/>, before ++ // <enabled/> is received. ++ outgoingStanzaCount++; ++ } ++ if (isSmEnabled) { ++ StreamHandlingPacket reqPacket = new StreamHandlingPacket("r", URN_SM_2); ++ mConnection.sendPacket(reqPacket); ++ ManagedPacket managedPacket = new ManagedPacket(); ++ managedPacket.packet = packet; ++ managedPacket.stanzaCount = outgoingStanzaCount; ++ sendList.add(managedPacket); ++ } ++ } ++ }, new PacketFilter() { ++ public boolean accept(Packet packet) { ++ return (!(packet instanceof StreamHandlingPacket)); ++ } ++ }); ++ ++ mConnection.addPacketListener(new PacketListener() { ++ public void processPacket(Packet packet) { ++ incomingStanzaCount++; ++ if (packet instanceof StreamHandlingPacket) { ++ StreamHandlingPacket shPacket = (StreamHandlingPacket) packet; ++ String name = shPacket.getElementName(); ++ if ("sm".equals(name)) { ++ isSmAvailable = true; ++ } ++ else if ("r".equals(name)) { ++ incomingStanzaCount--; ++ StreamHandlingPacket ackPacket = new StreamHandlingPacket("a", URN_SM_2); ++ ackPacket.addAttribute("h", String.valueOf(incomingStanzaCount)); ++ mConnection.sendPacket(ackPacket); ++ } ++ else if ("a".equals(name)) { ++ for (ManagedPacket mp : sendList) { ++ long ackedCount = Long.valueOf(((StreamHandlingPacket) packet).getAttribute("h")); ++ if (mp.stanzaCount <= ackedCount) { ++ sendList.remove(mp); ++ } ++ } ++ } ++ else if ("enabled".equals(name)) { ++ incomingStanzaCount = 0; ++ isSmEnabled = true; ++ mConnection.getRoster().setOfflineOnError(false); ++ String resume = shPacket.getAttribute("resume"); ++ if ("true".equals(resume) || "1".equals(resume)) { ++ sessionId = shPacket.getAttribute("id"); ++ } ++ sendList = new ArrayList<ManagedPacket>(); ++ } ++ else if ("resumed".equals(name)) { ++ incomingStanzaCount = previousIncomingStanzaCount; ++ isSmEnabled = true; ++ } ++ else if ("failed".equals(name)) { ++ // Failed, shutdown and the parent will retry ++ mConnection.getRoster().setOfflineOnError(true); ++ mConnection.getRoster().setOfflinePresences(); ++ sessionId = null; ++ mConnection.quickShutdown(); ++ // isSmEnabled is already false ++ } ++ } ++ } ++ }, new PacketFilter() { ++ public boolean accept(Packet packet) { ++ return true; ++ } ++ }); ++ } ++ ++ private static void addSimplePacketExtension(final String name, final String namespace) { ++ ProviderManager.getInstance().addExtensionProvider(name, namespace, new PacketExtensionProvider() { ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ StreamHandlingPacket packet = new StreamHandlingPacket(name, namespace); ++ int attributeCount = parser.getAttributeCount(); ++ for (int i = 0; i < attributeCount; i++) { ++ packet.addAttribute(parser.getAttributeName(i), parser.getAttributeValue(i)); ++ } ++ return packet; ++ } ++ }); ++ } ++ ++ static class StreamHandlingPacket extends UnknownPacket { ++ private String name; ++ private String namespace; ++ Map<String, String> attributes; ++ ++ StreamHandlingPacket(String name, String namespace) { ++ this.name = name; ++ this.namespace = namespace; ++ attributes = Collections.emptyMap(); ++ } ++ ++ public void addAttribute(String name, String value) { ++ if (attributes == Collections.EMPTY_MAP) ++ attributes = new HashMap<String, String>(); ++ attributes.put(name, value); ++ } ++ ++ public String getAttribute(String name) { ++ return attributes.get(name); ++ } ++ ++ public String getNamespace() { ++ return namespace; ++ } ++ ++ public String getElementName() { ++ return name; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()); ++ ++ // TODO Xmlns?? ++ if (getNamespace() != null) { ++ buf.append(" xmlns=\"").append(getNamespace()).append("\""); ++ } ++ for (String key : attributes.keySet()) { ++ buf.append(" ").append(key).append("=\"").append(StringUtils.escapeForXML(attributes.get(key))) ++ .append("\""); ++ } ++ buf.append("/>"); ++ return buf.toString(); ++ } ++ ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/BookmarkManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/BookmarkManager.java +new file mode 100644 +index 0000000..f85cc9c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/BookmarkManager.java +@@ -0,0 +1,224 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.bookmark; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smackx.PrivateDataManager; ++ ++import java.util.*; ++ ++/** ++ * Provides methods to manage bookmarks in accordance with JEP-0048. Methods for managing URLs and ++ * Conferences are provided. ++ * </p> ++ * It should be noted that some extensions have been made to the JEP. There is an attribute on URLs ++ * that marks a url as a news feed and also a sub-element can be added to either a URL or conference ++ * indicated that it is shared amongst all users on a server. ++ * ++ * @author Alexander Wenckus ++ */ ++public class BookmarkManager { ++ private static final Map<Connection, BookmarkManager> bookmarkManagerMap = new HashMap<Connection, BookmarkManager>(); ++ static { ++ PrivateDataManager.addPrivateDataProvider("storage", "storage:bookmarks", ++ new Bookmarks.Provider()); ++ } ++ ++ /** ++ * Returns the <i>BookmarkManager</i> for a connection, if it doesn't exist it is created. ++ * ++ * @param connection the connection for which the manager is desired. ++ * @return Returns the <i>BookmarkManager</i> for a connection, if it doesn't ++ * exist it is created. ++ * @throws XMPPException Thrown if the connection is null or has not yet been authenticated. ++ */ ++ public synchronized static BookmarkManager getBookmarkManager(Connection connection) ++ throws XMPPException ++ { ++ BookmarkManager manager = (BookmarkManager) bookmarkManagerMap.get(connection); ++ if(manager == null) { ++ manager = new BookmarkManager(connection); ++ bookmarkManagerMap.put(connection, manager); ++ } ++ return manager; ++ } ++ ++ private PrivateDataManager privateDataManager; ++ private Bookmarks bookmarks; ++ private final Object bookmarkLock = new Object(); ++ ++ /** ++ * Default constructor. Registers the data provider with the private data manager in the ++ * storage:bookmarks namespace. ++ * ++ * @param connection the connection for persisting and retrieving bookmarks. ++ * @throws XMPPException thrown when the connection is null or has not been authenticated. ++ */ ++ private BookmarkManager(Connection connection) throws XMPPException { ++ if(connection == null || !connection.isAuthenticated()) { ++ throw new XMPPException("Invalid connection."); ++ } ++ this.privateDataManager = new PrivateDataManager(connection); ++ } ++ ++ /** ++ * Returns all currently bookmarked conferences. ++ * ++ * @return returns all currently bookmarked conferences ++ * @throws XMPPException thrown when there was an error retrieving the current bookmarks from ++ * the server. ++ * @see BookmarkedConference ++ */ ++ public Collection<BookmarkedConference> getBookmarkedConferences() throws XMPPException { ++ retrieveBookmarks(); ++ return Collections.unmodifiableCollection(bookmarks.getBookmarkedConferences()); ++ } ++ ++ /** ++ * Adds or updates a conference in the bookmarks. ++ * ++ * @param name the name of the conference ++ * @param jid the jid of the conference ++ * @param isAutoJoin whether or not to join this conference automatically on login ++ * @param nickname the nickname to use for the user when joining the conference ++ * @param password the password to use for the user when joining the conference ++ * @throws XMPPException thrown when there is an issue retrieving the current bookmarks from ++ * the server. ++ */ ++ public void addBookmarkedConference(String name, String jid, boolean isAutoJoin, ++ String nickname, String password) throws XMPPException ++ { ++ retrieveBookmarks(); ++ BookmarkedConference bookmark ++ = new BookmarkedConference(name, jid, isAutoJoin, nickname, password); ++ List<BookmarkedConference> conferences = bookmarks.getBookmarkedConferences(); ++ if(conferences.contains(bookmark)) { ++ BookmarkedConference oldConference = conferences.get(conferences.indexOf(bookmark)); ++ if(oldConference.isShared()) { ++ throw new IllegalArgumentException("Cannot modify shared bookmark"); ++ } ++ oldConference.setAutoJoin(isAutoJoin); ++ oldConference.setName(name); ++ oldConference.setNickname(nickname); ++ oldConference.setPassword(password); ++ } ++ else { ++ bookmarks.addBookmarkedConference(bookmark); ++ } ++ privateDataManager.setPrivateData(bookmarks); ++ } ++ ++ /** ++ * Removes a conference from the bookmarks. ++ * ++ * @param jid the jid of the conference to be removed. ++ * @throws XMPPException thrown when there is a problem with the connection attempting to ++ * retrieve the bookmarks or persist the bookmarks. ++ * @throws IllegalArgumentException thrown when the conference being removed is a shared ++ * conference ++ */ ++ public void removeBookmarkedConference(String jid) throws XMPPException { ++ retrieveBookmarks(); ++ Iterator<BookmarkedConference> it = bookmarks.getBookmarkedConferences().iterator(); ++ while(it.hasNext()) { ++ BookmarkedConference conference = it.next(); ++ if(conference.getJid().equalsIgnoreCase(jid)) { ++ if(conference.isShared()) { ++ throw new IllegalArgumentException("Conference is shared and can't be removed"); ++ } ++ it.remove(); ++ privateDataManager.setPrivateData(bookmarks); ++ return; ++ } ++ } ++ } ++ ++ /** ++ * Returns an unmodifiable collection of all bookmarked urls. ++ * ++ * @return returns an unmodifiable collection of all bookmarked urls. ++ * @throws XMPPException thrown when there is a problem retriving bookmarks from the server. ++ */ ++ public Collection<BookmarkedURL> getBookmarkedURLs() throws XMPPException { ++ retrieveBookmarks(); ++ return Collections.unmodifiableCollection(bookmarks.getBookmarkedURLS()); ++ } ++ ++ /** ++ * Adds a new url or updates an already existing url in the bookmarks. ++ * ++ * @param URL the url of the bookmark ++ * @param name the name of the bookmark ++ * @param isRSS whether or not the url is an rss feed ++ * @throws XMPPException thrown when there is an error retriving or saving bookmarks from or to ++ * the server ++ */ ++ public void addBookmarkedURL(String URL, String name, boolean isRSS) throws XMPPException { ++ retrieveBookmarks(); ++ BookmarkedURL bookmark = new BookmarkedURL(URL, name, isRSS); ++ List<BookmarkedURL> urls = bookmarks.getBookmarkedURLS(); ++ if(urls.contains(bookmark)) { ++ BookmarkedURL oldURL = urls.get(urls.indexOf(bookmark)); ++ if(oldURL.isShared()) { ++ throw new IllegalArgumentException("Cannot modify shared bookmarks"); ++ } ++ oldURL.setName(name); ++ oldURL.setRss(isRSS); ++ } ++ else { ++ bookmarks.addBookmarkedURL(bookmark); ++ } ++ privateDataManager.setPrivateData(bookmarks); ++ } ++ ++ /** ++ * Removes a url from the bookmarks. ++ * ++ * @param bookmarkURL the url of the bookmark to remove ++ * @throws XMPPException thrown if there is an error retriving or saving bookmarks from or to ++ * the server. ++ */ ++ public void removeBookmarkedURL(String bookmarkURL) throws XMPPException { ++ retrieveBookmarks(); ++ Iterator<BookmarkedURL> it = bookmarks.getBookmarkedURLS().iterator(); ++ while(it.hasNext()) { ++ BookmarkedURL bookmark = it.next(); ++ if(bookmark.getURL().equalsIgnoreCase(bookmarkURL)) { ++ if(bookmark.isShared()) { ++ throw new IllegalArgumentException("Cannot delete a shared bookmark."); ++ } ++ it.remove(); ++ privateDataManager.setPrivateData(bookmarks); ++ return; ++ } ++ } ++ } ++ ++ private Bookmarks retrieveBookmarks() throws XMPPException { ++ synchronized(bookmarkLock) { ++ if(bookmarks == null) { ++ bookmarks = (Bookmarks) privateDataManager.getPrivateData("storage", ++ "storage:bookmarks"); ++ } ++ return bookmarks; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/BookmarkedConference.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/BookmarkedConference.java +new file mode 100644 +index 0000000..5dac202 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/BookmarkedConference.java +@@ -0,0 +1,130 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.bookmark; ++ ++/** ++ * Respresents a Conference Room bookmarked on the server using JEP-0048 Bookmark Storage JEP. ++ * ++ * @author Derek DeMoro ++ */ ++public class BookmarkedConference implements SharedBookmark { ++ ++ private String name; ++ private boolean autoJoin; ++ private final String jid; ++ ++ private String nickname; ++ private String password; ++ private boolean isShared; ++ ++ protected BookmarkedConference(String jid) { ++ this.jid = jid; ++ } ++ ++ protected BookmarkedConference(String name, String jid, boolean autoJoin, String nickname, ++ String password) ++ { ++ this.name = name; ++ this.jid = jid; ++ this.autoJoin = autoJoin; ++ this.nickname = nickname; ++ this.password = password; ++ } ++ ++ ++ /** ++ * Returns the display label representing the Conference room. ++ * ++ * @return the name of the conference room. ++ */ ++ public String getName() { ++ return name; ++ } ++ ++ protected void setName(String name) { ++ this.name = name; ++ } ++ ++ /** ++ * Returns true if this conference room should be auto-joined on startup. ++ * ++ * @return true if room should be joined on startup, otherwise false. ++ */ ++ public boolean isAutoJoin() { ++ return autoJoin; ++ } ++ ++ protected void setAutoJoin(boolean autoJoin) { ++ this.autoJoin = autoJoin; ++ } ++ ++ /** ++ * Returns the full JID of this conference room. (ex.dev@conference.jivesoftware.com) ++ * ++ * @return the full JID of this conference room. ++ */ ++ public String getJid() { ++ return jid; ++ } ++ ++ /** ++ * Returns the nickname to use when joining this conference room. This is an optional ++ * value and may return null. ++ * ++ * @return the nickname to use when joining, null may be returned. ++ */ ++ public String getNickname() { ++ return nickname; ++ } ++ ++ protected void setNickname(String nickname) { ++ this.nickname = nickname; ++ } ++ ++ /** ++ * Returns the password to use when joining this conference room. This is an optional ++ * value and may return null. ++ * ++ * @return the password to use when joining this conference room, null may be returned. ++ */ ++ public String getPassword() { ++ return password; ++ } ++ ++ protected void setPassword(String password) { ++ this.password = password; ++ } ++ ++ public boolean equals(Object obj) { ++ if(obj == null || !(obj instanceof BookmarkedConference)) { ++ return false; ++ } ++ BookmarkedConference conference = (BookmarkedConference)obj; ++ return conference.getJid().equalsIgnoreCase(jid); ++ } ++ ++ protected void setShared(boolean isShared) { ++ this.isShared = isShared; ++ } ++ ++ public boolean isShared() { ++ return isShared; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/BookmarkedURL.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/BookmarkedURL.java +new file mode 100644 +index 0000000..f3d6d9d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/BookmarkedURL.java +@@ -0,0 +1,104 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.bookmark; ++ ++/** ++ * Respresents one instance of a URL defined using JEP-0048 Bookmark Storage JEP. ++ * ++ * @author Derek DeMoro ++ */ ++public class BookmarkedURL implements SharedBookmark { ++ ++ private String name; ++ private final String URL; ++ private boolean isRss; ++ private boolean isShared; ++ ++ protected BookmarkedURL(String URL) { ++ this.URL = URL; ++ } ++ ++ protected BookmarkedURL(String URL, String name, boolean isRss) { ++ this.URL = URL; ++ this.name = name; ++ this.isRss = isRss; ++ } ++ ++ /** ++ * Returns the name representing the URL (eg. Jive Software). This can be used in as a label, or ++ * identifer in applications. ++ * ++ * @return the name reprenting the URL. ++ */ ++ public String getName() { ++ return name; ++ } ++ ++ /** ++ * Sets the name representing the URL. ++ * ++ * @param name the name. ++ */ ++ protected void setName(String name) { ++ this.name = name; ++ } ++ ++ /** ++ * Returns the URL. ++ * ++ * @return the url. ++ */ ++ public String getURL() { ++ return URL; ++ } ++ /** ++ * Set to true if this URL is an RSS or news feed. ++ * ++ * @param isRss True if the URL is a news feed and false if it is not. ++ */ ++ protected void setRss(boolean isRss) { ++ this.isRss = isRss; ++ } ++ ++ /** ++ * Returns true if this URL is a news feed. ++ * ++ * @return Returns true if this URL is a news feed. ++ */ ++ public boolean isRss() { ++ return isRss; ++ } ++ ++ public boolean equals(Object obj) { ++ if(!(obj instanceof BookmarkedURL)) { ++ return false; ++ } ++ BookmarkedURL url = (BookmarkedURL)obj; ++ return url.getURL().equalsIgnoreCase(URL); ++ } ++ ++ protected void setShared(boolean shared) { ++ this.isShared = shared; ++ } ++ ++ public boolean isShared() { ++ return isShared; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/Bookmarks.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/Bookmarks.java +new file mode 100644 +index 0000000..100fa46 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/Bookmarks.java +@@ -0,0 +1,310 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.bookmark; ++ ++import org.jivesoftware.smackx.packet.PrivateData; ++import org.jivesoftware.smackx.provider.PrivateDataProvider; ++import org.xmlpull.v1.XmlPullParser; ++import org.xmlpull.v1.XmlPullParserException; ++ ++import java.io.IOException; ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++ ++/** ++ * Bookmarks is used for storing and retrieving URLS and Conference rooms. ++ * Bookmark Storage (JEP-0048) defined a protocol for the storage of bookmarks to conference rooms and other entities ++ * in a Jabber user's account. ++ * See the following code sample for saving Bookmarks: ++ * <p/> ++ * <pre> ++ * Connection con = new XMPPConnection("jabber.org"); ++ * con.login("john", "doe"); ++ * Bookmarks bookmarks = new Bookmarks(); ++ * <p/> ++ * // Bookmark a URL ++ * BookmarkedURL url = new BookmarkedURL(); ++ * url.setName("Google"); ++ * url.setURL("http://www.jivesoftware.com"); ++ * bookmarks.addURL(url); ++ * // Bookmark a Conference room. ++ * BookmarkedConference conference = new BookmarkedConference(); ++ * conference.setName("My Favorite Room"); ++ * conference.setAutoJoin("true"); ++ * conference.setJID("dev@conference.jivesoftware.com"); ++ * bookmarks.addConference(conference); ++ * // Save Bookmarks using PrivateDataManager. ++ * PrivateDataManager manager = new PrivateDataManager(con); ++ * manager.setPrivateData(bookmarks); ++ * <p/> ++ * <p/> ++ * LastActivity activity = LastActivity.getLastActivity(con, "xray@jabber.org"); ++ * </pre> ++ * ++ * @author Derek DeMoro ++ */ ++public class Bookmarks implements PrivateData { ++ ++ private List<BookmarkedURL> bookmarkedURLS; ++ private List<BookmarkedConference> bookmarkedConferences; ++ ++ /** ++ * Required Empty Constructor to use Bookmarks. ++ */ ++ public Bookmarks() { ++ bookmarkedURLS = new ArrayList<BookmarkedURL>(); ++ bookmarkedConferences = new ArrayList<BookmarkedConference>(); ++ } ++ ++ /** ++ * Adds a BookmarkedURL. ++ * ++ * @param bookmarkedURL the bookmarked bookmarkedURL. ++ */ ++ public void addBookmarkedURL(BookmarkedURL bookmarkedURL) { ++ bookmarkedURLS.add(bookmarkedURL); ++ } ++ ++ /** ++ * Removes a bookmarked bookmarkedURL. ++ * ++ * @param bookmarkedURL the bookmarked bookmarkedURL to remove. ++ */ ++ public void removeBookmarkedURL(BookmarkedURL bookmarkedURL) { ++ bookmarkedURLS.remove(bookmarkedURL); ++ } ++ ++ /** ++ * Removes all BookmarkedURLs from user's bookmarks. ++ */ ++ public void clearBookmarkedURLS() { ++ bookmarkedURLS.clear(); ++ } ++ ++ /** ++ * Add a BookmarkedConference to bookmarks. ++ * ++ * @param bookmarkedConference the conference to remove. ++ */ ++ public void addBookmarkedConference(BookmarkedConference bookmarkedConference) { ++ bookmarkedConferences.add(bookmarkedConference); ++ } ++ ++ /** ++ * Removes a BookmarkedConference. ++ * ++ * @param bookmarkedConference the BookmarkedConference to remove. ++ */ ++ public void removeBookmarkedConference(BookmarkedConference bookmarkedConference) { ++ bookmarkedConferences.remove(bookmarkedConference); ++ } ++ ++ /** ++ * Removes all BookmarkedConferences from Bookmarks. ++ */ ++ public void clearBookmarkedConferences() { ++ bookmarkedConferences.clear(); ++ } ++ ++ /** ++ * Returns a Collection of all Bookmarked URLs for this user. ++ * ++ * @return a collection of all Bookmarked URLs. ++ */ ++ public List<BookmarkedURL> getBookmarkedURLS() { ++ return bookmarkedURLS; ++ } ++ ++ /** ++ * Returns a Collection of all Bookmarked Conference for this user. ++ * ++ * @return a collection of all Bookmarked Conferences. ++ */ ++ public List<BookmarkedConference> getBookmarkedConferences() { ++ return bookmarkedConferences; ++ } ++ ++ ++ /** ++ * Returns the root element name. ++ * ++ * @return the element name. ++ */ ++ public String getElementName() { ++ return "storage"; ++ } ++ ++ /** ++ * Returns the root element XML namespace. ++ * ++ * @return the namespace. ++ */ ++ public String getNamespace() { ++ return "storage:bookmarks"; ++ } ++ ++ /** ++ * Returns the XML reppresentation of the PrivateData. ++ * ++ * @return the private data as XML. ++ */ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<storage xmlns=\"storage:bookmarks\">"); ++ ++ final Iterator<BookmarkedURL> urls = getBookmarkedURLS().iterator(); ++ while (urls.hasNext()) { ++ BookmarkedURL urlStorage = urls.next(); ++ if(urlStorage.isShared()) { ++ continue; ++ } ++ buf.append("<url name=\"").append(urlStorage.getName()). ++ append("\" url=\"").append(urlStorage.getURL()).append("\""); ++ if(urlStorage.isRss()) { ++ buf.append(" rss=\"").append(true).append("\""); ++ } ++ buf.append(" />"); ++ } ++ ++ // Add Conference additions ++ final Iterator<BookmarkedConference> conferences = getBookmarkedConferences().iterator(); ++ while (conferences.hasNext()) { ++ BookmarkedConference conference = conferences.next(); ++ if(conference.isShared()) { ++ continue; ++ } ++ buf.append("<conference "); ++ buf.append("name=\"").append(conference.getName()).append("\" "); ++ buf.append("autojoin=\"").append(conference.isAutoJoin()).append("\" "); ++ buf.append("jid=\"").append(conference.getJid()).append("\" "); ++ buf.append(">"); ++ ++ if (conference.getNickname() != null) { ++ buf.append("<nick>").append(conference.getNickname()).append("</nick>"); ++ } ++ ++ ++ if (conference.getPassword() != null) { ++ buf.append("<password>").append(conference.getPassword()).append("</password>"); ++ } ++ buf.append("</conference>"); ++ } ++ ++ ++ buf.append("</storage>"); ++ return buf.toString(); ++ } ++ ++ /** ++ * The IQ Provider for BookmarkStorage. ++ * ++ * @author Derek DeMoro ++ */ ++ public static class Provider implements PrivateDataProvider { ++ ++ /** ++ * Empty Constructor for PrivateDataProvider. ++ */ ++ public Provider() { ++ super(); ++ } ++ ++ public PrivateData parsePrivateData(XmlPullParser parser) throws Exception { ++ Bookmarks storage = new Bookmarks(); ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG && "url".equals(parser.getName())) { ++ final BookmarkedURL urlStorage = getURLStorage(parser); ++ if (urlStorage != null) { ++ storage.addBookmarkedURL(urlStorage); ++ } ++ } ++ else if (eventType == XmlPullParser.START_TAG && ++ "conference".equals(parser.getName())) ++ { ++ final BookmarkedConference conference = getConferenceStorage(parser); ++ storage.addBookmarkedConference(conference); ++ } ++ else if (eventType == XmlPullParser.END_TAG && "storage".equals(parser.getName())) ++ { ++ done = true; ++ } ++ } ++ ++ ++ return storage; ++ } ++ } ++ ++ private static BookmarkedURL getURLStorage(XmlPullParser parser) throws IOException, XmlPullParserException { ++ String name = parser.getAttributeValue("", "name"); ++ String url = parser.getAttributeValue("", "url"); ++ String rssString = parser.getAttributeValue("", "rss"); ++ boolean rss = rssString != null && "true".equals(rssString); ++ ++ BookmarkedURL urlStore = new BookmarkedURL(url, name, rss); ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if(eventType == XmlPullParser.START_TAG ++ && "shared_bookmark".equals(parser.getName())) { ++ urlStore.setShared(true); ++ } ++ else if (eventType == XmlPullParser.END_TAG && "url".equals(parser.getName())) { ++ done = true; ++ } ++ } ++ return urlStore; ++ } ++ ++ private static BookmarkedConference getConferenceStorage(XmlPullParser parser) throws Exception { ++ String name = parser.getAttributeValue("", "name"); ++ String autojoin = parser.getAttributeValue("", "autojoin"); ++ String jid = parser.getAttributeValue("", "jid"); ++ ++ BookmarkedConference conf = new BookmarkedConference(jid); ++ conf.setName(name); ++ conf.setAutoJoin(Boolean.valueOf(autojoin).booleanValue()); ++ ++ // Check for nickname ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG && "nick".equals(parser.getName())) { ++ conf.setNickname(parser.nextText()); ++ } ++ else if (eventType == XmlPullParser.START_TAG && "password".equals(parser.getName())) { ++ conf.setPassword(parser.nextText()); ++ } ++ else if(eventType == XmlPullParser.START_TAG ++ && "shared_bookmark".equals(parser.getName())) { ++ conf.setShared(true); ++ } ++ else if (eventType == XmlPullParser.END_TAG && "conference".equals(parser.getName())) { ++ done = true; ++ } ++ } ++ ++ ++ return conf; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/SharedBookmark.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/SharedBookmark.java +new file mode 100644 +index 0000000..f672bc1 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/SharedBookmark.java +@@ -0,0 +1,35 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.bookmark; ++ ++/** ++ * Interface to indicate if a bookmark is shared across the server. ++ * ++ * @author Alexander Wenckus ++ */ ++public interface SharedBookmark { ++ ++ /** ++ * Returns true if this bookmark is shared. ++ * ++ * @return returns true if this bookmark is shared. ++ */ ++ public boolean isShared(); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamListener.java +new file mode 100644 +index 0000000..be78255 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamListener.java +@@ -0,0 +1,47 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams; ++ ++import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamListener; ++import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager; ++import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamListener; ++import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager; ++ ++/** ++ * BytestreamListener are notified if a remote user wants to initiate a bytestream. Implement this ++ * interface to handle incoming bytestream requests. ++ * <p> ++ * BytestreamListener can be registered at the {@link Socks5BytestreamManager} or the ++ * {@link InBandBytestreamManager}. ++ * <p> ++ * There are two ways to add this listener. See ++ * {@link BytestreamManager#addIncomingBytestreamListener(BytestreamListener)} and ++ * {@link BytestreamManager#addIncomingBytestreamListener(BytestreamListener, String)} for further ++ * details. ++ * <p> ++ * {@link Socks5BytestreamListener} or {@link InBandBytestreamListener} provide a more specific ++ * interface of the BytestreamListener. ++ * ++ * @author Henning Staib ++ */ ++public interface BytestreamListener { ++ ++ /** ++ * This listener is notified if a bytestream request from another user has been received. ++ * ++ * @param request the incoming bytestream request ++ */ ++ public void incomingBytestreamRequest(BytestreamRequest request); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamManager.java +new file mode 100644 +index 0000000..ca6bbc6 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamManager.java +@@ -0,0 +1,114 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams; ++ ++import java.io.IOException; ++ ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager; ++import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager; ++ ++/** ++ * BytestreamManager provides a generic interface for bytestream managers. ++ * <p> ++ * There are two implementations of the interface. See {@link Socks5BytestreamManager} and ++ * {@link InBandBytestreamManager}. ++ * ++ * @author Henning Staib ++ */ ++public interface BytestreamManager { ++ ++ /** ++ * Adds {@link BytestreamListener} that is called for every incoming bytestream request unless ++ * there is a user specific {@link BytestreamListener} registered. ++ * <p> ++ * See {@link Socks5BytestreamManager#addIncomingBytestreamListener(BytestreamListener)} and ++ * {@link InBandBytestreamManager#addIncomingBytestreamListener(BytestreamListener)} for further ++ * details. ++ * ++ * @param listener the listener to register ++ */ ++ public void addIncomingBytestreamListener(BytestreamListener listener); ++ ++ /** ++ * Removes the given listener from the list of listeners for all incoming bytestream requests. ++ * ++ * @param listener the listener to remove ++ */ ++ public void removeIncomingBytestreamListener(BytestreamListener listener); ++ ++ /** ++ * Adds {@link BytestreamListener} that is called for every incoming bytestream request unless ++ * there is a user specific {@link BytestreamListener} registered. ++ * <p> ++ * Use this method if you are awaiting an incoming bytestream request from a specific user. ++ * <p> ++ * See {@link Socks5BytestreamManager#addIncomingBytestreamListener(BytestreamListener, String)} ++ * and {@link InBandBytestreamManager#addIncomingBytestreamListener(BytestreamListener, String)} ++ * for further details. ++ * ++ * @param listener the listener to register ++ * @param initiatorJID the JID of the user that wants to establish a bytestream ++ */ ++ public void addIncomingBytestreamListener(BytestreamListener listener, String initiatorJID); ++ ++ /** ++ * Removes the listener for the given user. ++ * ++ * @param initiatorJID the JID of the user the listener should be removed ++ */ ++ public void removeIncomingBytestreamListener(String initiatorJID); ++ ++ /** ++ * Establishes a bytestream with the given user and returns the session to send/receive data ++ * to/from the user. ++ * <p> ++ * Use this method to establish bytestreams to users accepting all incoming bytestream requests ++ * since this method doesn't provide a way to tell the user something about the data to be sent. ++ * <p> ++ * To establish a bytestream after negotiation the kind of data to be sent (e.g. file transfer) ++ * use {@link #establishSession(String, String)}. ++ * <p> ++ * See {@link Socks5BytestreamManager#establishSession(String)} and ++ * {@link InBandBytestreamManager#establishSession(String)} for further details. ++ * ++ * @param targetJID the JID of the user a bytestream should be established ++ * @return the session to send/receive data to/from the user ++ * @throws XMPPException if an error occurred while establishing the session ++ * @throws IOException if an IO error occurred while establishing the session ++ * @throws InterruptedException if the thread was interrupted while waiting in a blocking ++ * operation ++ */ ++ public BytestreamSession establishSession(String targetJID) throws XMPPException, IOException, ++ InterruptedException; ++ ++ /** ++ * Establishes a bytestream with the given user and returns the session to send/receive data ++ * to/from the user. ++ * <p> ++ * See {@link Socks5BytestreamManager#establishSession(String)} and ++ * {@link InBandBytestreamManager#establishSession(String)} for further details. ++ * ++ * @param targetJID the JID of the user a bytestream should be established ++ * @param sessionID the session ID for the bytestream request ++ * @return the session to send/receive data to/from the user ++ * @throws XMPPException if an error occurred while establishing the session ++ * @throws IOException if an IO error occurred while establishing the session ++ * @throws InterruptedException if the thread was interrupted while waiting in a blocking ++ * operation ++ */ ++ public BytestreamSession establishSession(String targetJID, String sessionID) ++ throws XMPPException, IOException, InterruptedException; ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamRequest.java +new file mode 100644 +index 0000000..e368bad +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamRequest.java +@@ -0,0 +1,59 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams; ++ ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamRequest; ++import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamRequest; ++ ++/** ++ * BytestreamRequest provides an interface to handle incoming bytestream requests. ++ * <p> ++ * There are two implementations of the interface. See {@link Socks5BytestreamRequest} and ++ * {@link InBandBytestreamRequest}. ++ * ++ * @author Henning Staib ++ */ ++public interface BytestreamRequest { ++ ++ /** ++ * Returns the sender of the bytestream open request. ++ * ++ * @return the sender of the bytestream open request ++ */ ++ public String getFrom(); ++ ++ /** ++ * Returns the session ID of the bytestream open request. ++ * ++ * @return the session ID of the bytestream open request ++ */ ++ public String getSessionID(); ++ ++ /** ++ * Accepts the bytestream open request and returns the session to send/receive data. ++ * ++ * @return the session to send/receive data ++ * @throws XMPPException if an error occurred while accepting the bytestream request ++ * @throws InterruptedException if the thread was interrupted while waiting in a blocking ++ * operation ++ */ ++ public BytestreamSession accept() throws XMPPException, InterruptedException; ++ ++ /** ++ * Rejects the bytestream request by sending a reject error to the initiator. ++ */ ++ public void reject(); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamSession.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamSession.java +new file mode 100644 +index 0000000..7aafc35 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamSession.java +@@ -0,0 +1,81 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams; ++ ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.OutputStream; ++ ++import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamSession; ++import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamSession; ++ ++/** ++ * BytestreamSession provides an interface for established bytestream sessions. ++ * <p> ++ * There are two implementations of the interface. See {@link Socks5BytestreamSession} and ++ * {@link InBandBytestreamSession}. ++ * ++ * @author Henning Staib ++ */ ++public interface BytestreamSession { ++ ++ /** ++ * Returns the InputStream associated with this session to send data. ++ * ++ * @return the InputStream associated with this session to send data ++ * @throws IOException if an error occurs while retrieving the input stream ++ */ ++ public InputStream getInputStream() throws IOException; ++ ++ /** ++ * Returns the OutputStream associated with this session to receive data. ++ * ++ * @return the OutputStream associated with this session to receive data ++ * @throws IOException if an error occurs while retrieving the output stream ++ */ ++ public OutputStream getOutputStream() throws IOException; ++ ++ /** ++ * Closes the bytestream session. ++ * <p> ++ * Closing the session will also close the input stream and the output stream associated to this ++ * session. ++ * ++ * @throws IOException if an error occurs while closing the session ++ */ ++ public void close() throws IOException; ++ ++ /** ++ * Returns the timeout for read operations of the input stream associated with this session. 0 ++ * returns implies that the option is disabled (i.e., timeout of infinity). Default is 0. ++ * ++ * @return the timeout for read operations ++ * @throws IOException if there is an error in the underlying protocol ++ */ ++ public int getReadTimeout() throws IOException; ++ ++ /** ++ * Sets the specified timeout, in milliseconds. With this option set to a non-zero timeout, a ++ * read() call on the input stream associated with this session will block for only this amount ++ * of time. If the timeout expires, a java.net.SocketTimeoutException is raised, though the ++ * session is still valid. The option must be enabled prior to entering the blocking operation ++ * to have effect. The timeout must be > 0. A timeout of zero is interpreted as an infinite ++ * timeout. Default is 0. ++ * ++ * @param timeout the specified timeout, in milliseconds ++ * @throws IOException if there is an error in the underlying protocol ++ */ ++ public void setReadTimeout(int timeout) throws IOException; ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/CloseListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/CloseListener.java +new file mode 100644 +index 0000000..7690e95 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/CloseListener.java +@@ -0,0 +1,75 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.ibb; ++ ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.filter.AndFilter; ++import org.jivesoftware.smack.filter.IQTypeFilter; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smackx.bytestreams.ibb.packet.Close; ++ ++/** ++ * CloseListener handles all In-Band Bytestream close requests. ++ * <p> ++ * If a close request is received it looks if a stored In-Band Bytestream ++ * session exists and closes it. If no session with the given session ID exists ++ * an <item-not-found/> error is returned to the sender. ++ * ++ * @author Henning Staib ++ */ ++class CloseListener implements PacketListener { ++ ++ /* manager containing the listeners and the XMPP connection */ ++ private final InBandBytestreamManager manager; ++ ++ /* packet filter for all In-Band Bytestream close requests */ ++ private final PacketFilter closeFilter = new AndFilter(new PacketTypeFilter( ++ Close.class), new IQTypeFilter(IQ.Type.SET)); ++ ++ /** ++ * Constructor. ++ * ++ * @param manager the In-Band Bytestream manager ++ */ ++ protected CloseListener(InBandBytestreamManager manager) { ++ this.manager = manager; ++ } ++ ++ public void processPacket(Packet packet) { ++ Close closeRequest = (Close) packet; ++ InBandBytestreamSession ibbSession = this.manager.getSessions().get( ++ closeRequest.getSessionID()); ++ if (ibbSession == null) { ++ this.manager.replyItemNotFoundPacket(closeRequest); ++ } ++ else { ++ ibbSession.closeByPeer(closeRequest); ++ this.manager.getSessions().remove(closeRequest.getSessionID()); ++ } ++ ++ } ++ ++ /** ++ * Returns the packet filter for In-Band Bytestream close requests. ++ * ++ * @return the packet filter for In-Band Bytestream close requests ++ */ ++ protected PacketFilter getFilter() { ++ return this.closeFilter; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/DataListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/DataListener.java +new file mode 100644 +index 0000000..166c146 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/DataListener.java +@@ -0,0 +1,73 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.ibb; ++ ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.filter.AndFilter; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smackx.bytestreams.ibb.packet.Data; ++ ++/** ++ * DataListener handles all In-Band Bytestream IQ stanzas containing a data ++ * packet extension that don't belong to an existing session. ++ * <p> ++ * If a data packet is received it looks if a stored In-Band Bytestream session ++ * exists. If no session with the given session ID exists an ++ * <item-not-found/> error is returned to the sender. ++ * <p> ++ * Data packets belonging to a running In-Band Bytestream session are processed ++ * by more specific listeners registered when an {@link InBandBytestreamSession} ++ * is created. ++ * ++ * @author Henning Staib ++ */ ++class DataListener implements PacketListener { ++ ++ /* manager containing the listeners and the XMPP connection */ ++ private final InBandBytestreamManager manager; ++ ++ /* packet filter for all In-Band Bytestream data packets */ ++ private final PacketFilter dataFilter = new AndFilter( ++ new PacketTypeFilter(Data.class)); ++ ++ /** ++ * Constructor. ++ * ++ * @param manager the In-Band Bytestream manager ++ */ ++ public DataListener(InBandBytestreamManager manager) { ++ this.manager = manager; ++ } ++ ++ public void processPacket(Packet packet) { ++ Data data = (Data) packet; ++ InBandBytestreamSession ibbSession = this.manager.getSessions().get( ++ data.getDataPacketExtension().getSessionID()); ++ if (ibbSession == null) { ++ this.manager.replyItemNotFoundPacket(data); ++ } ++ } ++ ++ /** ++ * Returns the packet filter for In-Band Bytestream data packets. ++ * ++ * @return the packet filter for In-Band Bytestream data packets ++ */ ++ protected PacketFilter getFilter() { ++ return this.dataFilter; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamListener.java +new file mode 100644 +index 0000000..68791a6 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamListener.java +@@ -0,0 +1,46 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.ibb; ++ ++import org.jivesoftware.smackx.bytestreams.BytestreamListener; ++import org.jivesoftware.smackx.bytestreams.BytestreamRequest; ++ ++/** ++ * InBandBytestreamListener are informed if a remote user wants to initiate an In-Band Bytestream. ++ * Implement this interface to handle incoming In-Band Bytestream requests. ++ * <p> ++ * There are two ways to add this listener. See ++ * {@link InBandBytestreamManager#addIncomingBytestreamListener(BytestreamListener)} and ++ * {@link InBandBytestreamManager#addIncomingBytestreamListener(BytestreamListener, String)} for ++ * further details. ++ * ++ * @author Henning Staib ++ */ ++public abstract class InBandBytestreamListener implements BytestreamListener { ++ ++ ++ ++ public void incomingBytestreamRequest(BytestreamRequest request) { ++ incomingBytestreamRequest((InBandBytestreamRequest) request); ++ } ++ ++ /** ++ * This listener is notified if an In-Band Bytestream request from another user has been ++ * received. ++ * ++ * @param request the incoming In-Band Bytestream request ++ */ ++ public abstract void incomingBytestreamRequest(InBandBytestreamRequest request); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamManager.java +new file mode 100644 +index 0000000..ef52531 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamManager.java +@@ -0,0 +1,546 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.ibb; ++ ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Map; ++import java.util.Random; ++import java.util.concurrent.ConcurrentHashMap; ++ ++import org.jivesoftware.smack.AbstractConnectionListener; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.ConnectionCreationListener; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.XMPPError; ++import org.jivesoftware.smack.util.SyncPacketSend; ++import org.jivesoftware.smackx.bytestreams.BytestreamListener; ++import org.jivesoftware.smackx.bytestreams.BytestreamManager; ++import org.jivesoftware.smackx.bytestreams.ibb.packet.Open; ++import org.jivesoftware.smackx.filetransfer.FileTransferManager; ++ ++/** ++ * The InBandBytestreamManager class handles establishing In-Band Bytestreams as specified in the <a ++ * href="http://xmpp.org/extensions/xep-0047.html">XEP-0047</a>. ++ * <p> ++ * The In-Band Bytestreams (IBB) enables two entities to establish a virtual bytestream over which ++ * they can exchange Base64-encoded chunks of data over XMPP itself. It is the fall-back mechanism ++ * in case the Socks5 bytestream method of transferring data is not available. ++ * <p> ++ * There are two ways to send data over an In-Band Bytestream. It could either use IQ stanzas to ++ * send data packets or message stanzas. If IQ stanzas are used every data packet is acknowledged by ++ * the receiver. This is the recommended way to avoid possible rate-limiting penalties. Message ++ * stanzas are not acknowledged because most XMPP server implementation don't support stanza ++ * flow-control method like <a href="http://xmpp.org/extensions/xep-0079.html">Advanced Message ++ * Processing</a>. To set the stanza that should be used invoke {@link #setStanza(StanzaType)}. ++ * <p> ++ * To establish an In-Band Bytestream invoke the {@link #establishSession(String)} method. This will ++ * negotiate an in-band bytestream with the given target JID and return a session. ++ * <p> ++ * If a session ID for the In-Band Bytestream was already negotiated (e.g. while negotiating a file ++ * transfer) invoke {@link #establishSession(String, String)}. ++ * <p> ++ * To handle incoming In-Band Bytestream requests add an {@link InBandBytestreamListener} to the ++ * manager. There are two ways to add this listener. If you want to be informed about incoming ++ * In-Band Bytestreams from a specific user add the listener by invoking ++ * {@link #addIncomingBytestreamListener(BytestreamListener, String)}. If the listener should ++ * respond to all In-Band Bytestream requests invoke ++ * {@link #addIncomingBytestreamListener(BytestreamListener)}. ++ * <p> ++ * Note that the registered {@link InBandBytestreamListener} will NOT be notified on incoming ++ * In-Band bytestream requests sent in the context of <a ++ * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See ++ * {@link FileTransferManager}) ++ * <p> ++ * If no {@link InBandBytestreamListener}s are registered, all incoming In-Band bytestream requests ++ * will be rejected by returning a <not-acceptable/> error to the initiator. ++ * ++ * @author Henning Staib ++ */ ++public class InBandBytestreamManager implements BytestreamManager { ++ ++ /** ++ * Stanzas that can be used to encapsulate In-Band Bytestream data packets. ++ */ ++ public enum StanzaType { ++ ++ /** ++ * IQ stanza. ++ */ ++ IQ, ++ ++ /** ++ * Message stanza. ++ */ ++ MESSAGE ++ } ++ ++ /* ++ * create a new InBandBytestreamManager and register its shutdown listener on every established ++ * connection ++ */ ++ static { ++ Connection.addConnectionCreationListener(new ConnectionCreationListener() { ++ public void connectionCreated(Connection connection) { ++ final InBandBytestreamManager manager; ++ manager = InBandBytestreamManager.getByteStreamManager(connection); ++ ++ // register shutdown listener ++ connection.addConnectionListener(new AbstractConnectionListener() { ++ ++ public void connectionClosed() { ++ manager.disableService(); ++ } ++ ++ }); ++ ++ } ++ }); ++ } ++ ++ /** ++ * The XMPP namespace of the In-Band Bytestream ++ */ ++ public static final String NAMESPACE = "http://jabber.org/protocol/ibb"; ++ ++ /** ++ * Maximum block size that is allowed for In-Band Bytestreams ++ */ ++ public static final int MAXIMUM_BLOCK_SIZE = 65535; ++ ++ /* prefix used to generate session IDs */ ++ private static final String SESSION_ID_PREFIX = "jibb_"; ++ ++ /* random generator to create session IDs */ ++ private final static Random randomGenerator = new Random(); ++ ++ /* stores one InBandBytestreamManager for each XMPP connection */ ++ private final static Map<Connection, InBandBytestreamManager> managers = new HashMap<Connection, InBandBytestreamManager>(); ++ ++ /* XMPP connection */ ++ private final Connection connection; ++ ++ /* ++ * assigns a user to a listener that is informed if an In-Band Bytestream request for this user ++ * is received ++ */ ++ private final Map<String, BytestreamListener> userListeners = new ConcurrentHashMap<String, BytestreamListener>(); ++ ++ /* ++ * list of listeners that respond to all In-Band Bytestream requests if there are no user ++ * specific listeners for that request ++ */ ++ private final List<BytestreamListener> allRequestListeners = Collections.synchronizedList(new LinkedList<BytestreamListener>()); ++ ++ /* listener that handles all incoming In-Band Bytestream requests */ ++ private final InitiationListener initiationListener; ++ ++ /* listener that handles all incoming In-Band Bytestream IQ data packets */ ++ private final DataListener dataListener; ++ ++ /* listener that handles all incoming In-Band Bytestream close requests */ ++ private final CloseListener closeListener; ++ ++ /* assigns a session ID to the In-Band Bytestream session */ ++ private final Map<String, InBandBytestreamSession> sessions = new ConcurrentHashMap<String, InBandBytestreamSession>(); ++ ++ /* block size used for new In-Band Bytestreams */ ++ private int defaultBlockSize = 4096; ++ ++ /* maximum block size allowed for this connection */ ++ private int maximumBlockSize = MAXIMUM_BLOCK_SIZE; ++ ++ /* the stanza used to send data packets */ ++ private StanzaType stanza = StanzaType.IQ; ++ ++ /* ++ * list containing session IDs of In-Band Bytestream open packets that should be ignored by the ++ * InitiationListener ++ */ ++ private List<String> ignoredBytestreamRequests = Collections.synchronizedList(new LinkedList<String>()); ++ ++ /** ++ * Returns the InBandBytestreamManager to handle In-Band Bytestreams for a given ++ * {@link Connection}. ++ * ++ * @param connection the XMPP connection ++ * @return the InBandBytestreamManager for the given XMPP connection ++ */ ++ public static synchronized InBandBytestreamManager getByteStreamManager(Connection connection) { ++ if (connection == null) ++ return null; ++ InBandBytestreamManager manager = managers.get(connection); ++ if (manager == null) { ++ manager = new InBandBytestreamManager(connection); ++ managers.put(connection, manager); ++ } ++ return manager; ++ } ++ ++ /** ++ * Constructor. ++ * ++ * @param connection the XMPP connection ++ */ ++ private InBandBytestreamManager(Connection connection) { ++ this.connection = connection; ++ ++ // register bytestream open packet listener ++ this.initiationListener = new InitiationListener(this); ++ this.connection.addPacketListener(this.initiationListener, ++ this.initiationListener.getFilter()); ++ ++ // register bytestream data packet listener ++ this.dataListener = new DataListener(this); ++ this.connection.addPacketListener(this.dataListener, this.dataListener.getFilter()); ++ ++ // register bytestream close packet listener ++ this.closeListener = new CloseListener(this); ++ this.connection.addPacketListener(this.closeListener, this.closeListener.getFilter()); ++ ++ } ++ ++ /** ++ * Adds InBandBytestreamListener that is called for every incoming in-band bytestream request ++ * unless there is a user specific InBandBytestreamListener registered. ++ * <p> ++ * If no listeners are registered all In-Band Bytestream request are rejected with a ++ * <not-acceptable/> error. ++ * <p> ++ * Note that the registered {@link InBandBytestreamListener} will NOT be notified on incoming ++ * Socks5 bytestream requests sent in the context of <a ++ * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See ++ * {@link FileTransferManager}) ++ * ++ * @param listener the listener to register ++ */ ++ public void addIncomingBytestreamListener(BytestreamListener listener) { ++ this.allRequestListeners.add(listener); ++ } ++ ++ /** ++ * Removes the given listener from the list of listeners for all incoming In-Band Bytestream ++ * requests. ++ * ++ * @param listener the listener to remove ++ */ ++ public void removeIncomingBytestreamListener(BytestreamListener listener) { ++ this.allRequestListeners.remove(listener); ++ } ++ ++ /** ++ * Adds InBandBytestreamListener that is called for every incoming in-band bytestream request ++ * from the given user. ++ * <p> ++ * Use this method if you are awaiting an incoming Socks5 bytestream request from a specific ++ * user. ++ * <p> ++ * If no listeners are registered all In-Band Bytestream request are rejected with a ++ * <not-acceptable/> error. ++ * <p> ++ * Note that the registered {@link InBandBytestreamListener} will NOT be notified on incoming ++ * Socks5 bytestream requests sent in the context of <a ++ * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See ++ * {@link FileTransferManager}) ++ * ++ * @param listener the listener to register ++ * @param initiatorJID the JID of the user that wants to establish an In-Band Bytestream ++ */ ++ public void addIncomingBytestreamListener(BytestreamListener listener, String initiatorJID) { ++ this.userListeners.put(initiatorJID, listener); ++ } ++ ++ /** ++ * Removes the listener for the given user. ++ * ++ * @param initiatorJID the JID of the user the listener should be removed ++ */ ++ public void removeIncomingBytestreamListener(String initiatorJID) { ++ this.userListeners.remove(initiatorJID); ++ } ++ ++ /** ++ * Use this method to ignore the next incoming In-Band Bytestream request containing the given ++ * session ID. No listeners will be notified for this request and and no error will be returned ++ * to the initiator. ++ * <p> ++ * This method should be used if you are awaiting an In-Band Bytestream request as a reply to ++ * another packet (e.g. file transfer). ++ * ++ * @param sessionID to be ignored ++ */ ++ public void ignoreBytestreamRequestOnce(String sessionID) { ++ this.ignoredBytestreamRequests.add(sessionID); ++ } ++ ++ /** ++ * Returns the default block size that is used for all outgoing in-band bytestreams for this ++ * connection. ++ * <p> ++ * The recommended default block size is 4096 bytes. See <a ++ * href="http://xmpp.org/extensions/xep-0047.html#usage">XEP-0047</a> Section 5. ++ * ++ * @return the default block size ++ */ ++ public int getDefaultBlockSize() { ++ return defaultBlockSize; ++ } ++ ++ /** ++ * Sets the default block size that is used for all outgoing in-band bytestreams for this ++ * connection. ++ * <p> ++ * The default block size must be between 1 and 65535 bytes. The recommended default block size ++ * is 4096 bytes. See <a href="http://xmpp.org/extensions/xep-0047.html#usage">XEP-0047</a> ++ * Section 5. ++ * ++ * @param defaultBlockSize the default block size to set ++ */ ++ public void setDefaultBlockSize(int defaultBlockSize) { ++ if (defaultBlockSize <= 0 || defaultBlockSize > MAXIMUM_BLOCK_SIZE) { ++ throw new IllegalArgumentException("Default block size must be between 1 and " ++ + MAXIMUM_BLOCK_SIZE); ++ } ++ this.defaultBlockSize = defaultBlockSize; ++ } ++ ++ /** ++ * Returns the maximum block size that is allowed for In-Band Bytestreams for this connection. ++ * <p> ++ * Incoming In-Band Bytestream open request will be rejected with an ++ * <resource-constraint/> error if the block size is greater then the maximum allowed ++ * block size. ++ * <p> ++ * The default maximum block size is 65535 bytes. ++ * ++ * @return the maximum block size ++ */ ++ public int getMaximumBlockSize() { ++ return maximumBlockSize; ++ } ++ ++ /** ++ * Sets the maximum block size that is allowed for In-Band Bytestreams for this connection. ++ * <p> ++ * The maximum block size must be between 1 and 65535 bytes. ++ * <p> ++ * Incoming In-Band Bytestream open request will be rejected with an ++ * <resource-constraint/> error if the block size is greater then the maximum allowed ++ * block size. ++ * ++ * @param maximumBlockSize the maximum block size to set ++ */ ++ public void setMaximumBlockSize(int maximumBlockSize) { ++ if (maximumBlockSize <= 0 || maximumBlockSize > MAXIMUM_BLOCK_SIZE) { ++ throw new IllegalArgumentException("Maximum block size must be between 1 and " ++ + MAXIMUM_BLOCK_SIZE); ++ } ++ this.maximumBlockSize = maximumBlockSize; ++ } ++ ++ /** ++ * Returns the stanza used to send data packets. ++ * <p> ++ * Default is {@link StanzaType#IQ}. See <a ++ * href="http://xmpp.org/extensions/xep-0047.html#message">XEP-0047</a> Section 4. ++ * ++ * @return the stanza used to send data packets ++ */ ++ public StanzaType getStanza() { ++ return stanza; ++ } ++ ++ /** ++ * Sets the stanza used to send data packets. ++ * <p> ++ * The use of {@link StanzaType#IQ} is recommended. See <a ++ * href="http://xmpp.org/extensions/xep-0047.html#message">XEP-0047</a> Section 4. ++ * ++ * @param stanza the stanza to set ++ */ ++ public void setStanza(StanzaType stanza) { ++ this.stanza = stanza; ++ } ++ ++ /** ++ * Establishes an In-Band Bytestream with the given user and returns the session to send/receive ++ * data to/from the user. ++ * <p> ++ * Use this method to establish In-Band Bytestreams to users accepting all incoming In-Band ++ * Bytestream requests since this method doesn't provide a way to tell the user something about ++ * the data to be sent. ++ * <p> ++ * To establish an In-Band Bytestream after negotiation the kind of data to be sent (e.g. file ++ * transfer) use {@link #establishSession(String, String)}. ++ * ++ * @param targetJID the JID of the user an In-Band Bytestream should be established ++ * @return the session to send/receive data to/from the user ++ * @throws XMPPException if the user doesn't support or accept in-band bytestreams, or if the ++ * user prefers smaller block sizes ++ */ ++ public InBandBytestreamSession establishSession(String targetJID) throws XMPPException { ++ String sessionID = getNextSessionID(); ++ return establishSession(targetJID, sessionID); ++ } ++ ++ /** ++ * Establishes an In-Band Bytestream with the given user using the given session ID and returns ++ * the session to send/receive data to/from the user. ++ * ++ * @param targetJID the JID of the user an In-Band Bytestream should be established ++ * @param sessionID the session ID for the In-Band Bytestream request ++ * @return the session to send/receive data to/from the user ++ * @throws XMPPException if the user doesn't support or accept in-band bytestreams, or if the ++ * user prefers smaller block sizes ++ */ ++ public InBandBytestreamSession establishSession(String targetJID, String sessionID) ++ throws XMPPException { ++ Open byteStreamRequest = new Open(sessionID, this.defaultBlockSize, this.stanza); ++ byteStreamRequest.setTo(targetJID); ++ ++ // sending packet will throw exception on timeout or error reply ++ SyncPacketSend.getReply(this.connection, byteStreamRequest); ++ ++ InBandBytestreamSession inBandBytestreamSession = new InBandBytestreamSession( ++ this.connection, byteStreamRequest, targetJID); ++ this.sessions.put(sessionID, inBandBytestreamSession); ++ ++ return inBandBytestreamSession; ++ } ++ ++ /** ++ * Responses to the given IQ packet's sender with an XMPP error that an In-Band Bytestream is ++ * not accepted. ++ * ++ * @param request IQ packet that should be answered with a not-acceptable error ++ */ ++ protected void replyRejectPacket(IQ request) { ++ XMPPError xmppError = new XMPPError(XMPPError.Condition.no_acceptable); ++ IQ error = IQ.createErrorResponse(request, xmppError); ++ this.connection.sendPacket(error); ++ } ++ ++ /** ++ * Responses to the given IQ packet's sender with an XMPP error that an In-Band Bytestream open ++ * request is rejected because its block size is greater than the maximum allowed block size. ++ * ++ * @param request IQ packet that should be answered with a resource-constraint error ++ */ ++ protected void replyResourceConstraintPacket(IQ request) { ++ XMPPError xmppError = new XMPPError(XMPPError.Condition.resource_constraint); ++ IQ error = IQ.createErrorResponse(request, xmppError); ++ this.connection.sendPacket(error); ++ } ++ ++ /** ++ * Responses to the given IQ packet's sender with an XMPP error that an In-Band Bytestream ++ * session could not be found. ++ * ++ * @param request IQ packet that should be answered with a item-not-found error ++ */ ++ protected void replyItemNotFoundPacket(IQ request) { ++ XMPPError xmppError = new XMPPError(XMPPError.Condition.item_not_found); ++ IQ error = IQ.createErrorResponse(request, xmppError); ++ this.connection.sendPacket(error); ++ } ++ ++ /** ++ * Returns a new unique session ID. ++ * ++ * @return a new unique session ID ++ */ ++ private String getNextSessionID() { ++ StringBuilder buffer = new StringBuilder(); ++ buffer.append(SESSION_ID_PREFIX); ++ buffer.append(Math.abs(randomGenerator.nextLong())); ++ return buffer.toString(); ++ } ++ ++ /** ++ * Returns the XMPP connection. ++ * ++ * @return the XMPP connection ++ */ ++ protected Connection getConnection() { ++ return this.connection; ++ } ++ ++ /** ++ * Returns the {@link InBandBytestreamListener} that should be informed if a In-Band Bytestream ++ * request from the given initiator JID is received. ++ * ++ * @param initiator the initiator's JID ++ * @return the listener ++ */ ++ protected BytestreamListener getUserListener(String initiator) { ++ return this.userListeners.get(initiator); ++ } ++ ++ /** ++ * Returns a list of {@link InBandBytestreamListener} that are informed if there are no ++ * listeners for a specific initiator. ++ * ++ * @return list of listeners ++ */ ++ protected List<BytestreamListener> getAllRequestListeners() { ++ return this.allRequestListeners; ++ } ++ ++ /** ++ * Returns the sessions map. ++ * ++ * @return the sessions map ++ */ ++ protected Map<String, InBandBytestreamSession> getSessions() { ++ return sessions; ++ } ++ ++ /** ++ * Returns the list of session IDs that should be ignored by the InitialtionListener ++ * ++ * @return list of session IDs ++ */ ++ protected List<String> getIgnoredBytestreamRequests() { ++ return ignoredBytestreamRequests; ++ } ++ ++ /** ++ * Disables the InBandBytestreamManager by removing its packet listeners and resetting its ++ * internal status. ++ */ ++ private void disableService() { ++ ++ // remove manager from static managers map ++ managers.remove(connection); ++ ++ // remove all listeners registered by this manager ++ this.connection.removePacketListener(this.initiationListener); ++ this.connection.removePacketListener(this.dataListener); ++ this.connection.removePacketListener(this.closeListener); ++ ++ // shutdown threads ++ this.initiationListener.shutdown(); ++ ++ // reset internal status ++ this.userListeners.clear(); ++ this.allRequestListeners.clear(); ++ this.sessions.clear(); ++ this.ignoredBytestreamRequests.clear(); ++ ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamRequest.java +new file mode 100644 +index 0000000..5bc689a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamRequest.java +@@ -0,0 +1,92 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.ibb; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smackx.bytestreams.BytestreamRequest; ++import org.jivesoftware.smackx.bytestreams.ibb.packet.Open; ++ ++/** ++ * InBandBytestreamRequest class handles incoming In-Band Bytestream requests. ++ * ++ * @author Henning Staib ++ */ ++public class InBandBytestreamRequest implements BytestreamRequest { ++ ++ /* the bytestream initialization request */ ++ private final Open byteStreamRequest; ++ ++ /* ++ * In-Band Bytestream manager containing the XMPP connection and helper ++ * methods ++ */ ++ private final InBandBytestreamManager manager; ++ ++ protected InBandBytestreamRequest(InBandBytestreamManager manager, ++ Open byteStreamRequest) { ++ this.manager = manager; ++ this.byteStreamRequest = byteStreamRequest; ++ } ++ ++ /** ++ * Returns the sender of the In-Band Bytestream open request. ++ * ++ * @return the sender of the In-Band Bytestream open request ++ */ ++ public String getFrom() { ++ return this.byteStreamRequest.getFrom(); ++ } ++ ++ /** ++ * Returns the session ID of the In-Band Bytestream open request. ++ * ++ * @return the session ID of the In-Band Bytestream open request ++ */ ++ public String getSessionID() { ++ return this.byteStreamRequest.getSessionID(); ++ } ++ ++ /** ++ * Accepts the In-Band Bytestream open request and returns the session to ++ * send/receive data. ++ * ++ * @return the session to send/receive data ++ * @throws XMPPException if stream is invalid. ++ */ ++ public InBandBytestreamSession accept() throws XMPPException { ++ Connection connection = this.manager.getConnection(); ++ ++ // create In-Band Bytestream session and store it ++ InBandBytestreamSession ibbSession = new InBandBytestreamSession(connection, ++ this.byteStreamRequest, this.byteStreamRequest.getFrom()); ++ this.manager.getSessions().put(this.byteStreamRequest.getSessionID(), ibbSession); ++ ++ // acknowledge request ++ IQ resultIQ = IQ.createResultIQ(this.byteStreamRequest); ++ connection.sendPacket(resultIQ); ++ ++ return ibbSession; ++ } ++ ++ /** ++ * Rejects the In-Band Bytestream request by sending a reject error to the ++ * initiator. ++ */ ++ public void reject() { ++ this.manager.replyRejectPacket(this.byteStreamRequest); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamSession.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamSession.java +new file mode 100644 +index 0000000..1cced78 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamSession.java +@@ -0,0 +1,795 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.ibb; ++ ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.OutputStream; ++import java.net.SocketTimeoutException; ++import java.util.concurrent.BlockingQueue; ++import java.util.concurrent.LinkedBlockingQueue; ++import java.util.concurrent.TimeUnit; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.AndFilter; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Message; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.packet.XMPPError; ++import org.jivesoftware.smack.util.StringUtils; ++import org.jivesoftware.smack.util.SyncPacketSend; ++import org.jivesoftware.smackx.bytestreams.BytestreamSession; ++import org.jivesoftware.smackx.bytestreams.ibb.packet.Close; ++import org.jivesoftware.smackx.bytestreams.ibb.packet.Data; ++import org.jivesoftware.smackx.bytestreams.ibb.packet.DataPacketExtension; ++import org.jivesoftware.smackx.bytestreams.ibb.packet.Open; ++ ++/** ++ * InBandBytestreamSession class represents an In-Band Bytestream session. ++ * <p> ++ * In-band bytestreams are bidirectional and this session encapsulates the streams for both ++ * directions. ++ * <p> ++ * Note that closing the In-Band Bytestream session will close both streams. If both streams are ++ * closed individually the session will be closed automatically once the second stream is closed. ++ * Use the {@link #setCloseBothStreamsEnabled(boolean)} method if both streams should be closed ++ * automatically if one of them is closed. ++ * ++ * @author Henning Staib ++ */ ++public class InBandBytestreamSession implements BytestreamSession { ++ ++ /* XMPP connection */ ++ private final Connection connection; ++ ++ /* the In-Band Bytestream open request for this session */ ++ private final Open byteStreamRequest; ++ ++ /* ++ * the input stream for this session (either IQIBBInputStream or MessageIBBInputStream) ++ */ ++ private IBBInputStream inputStream; ++ ++ /* ++ * the output stream for this session (either IQIBBOutputStream or MessageIBBOutputStream) ++ */ ++ private IBBOutputStream outputStream; ++ ++ /* JID of the remote peer */ ++ private String remoteJID; ++ ++ /* flag to close both streams if one of them is closed */ ++ private boolean closeBothStreamsEnabled = false; ++ ++ /* flag to indicate if session is closed */ ++ private boolean isClosed = false; ++ ++ /** ++ * Constructor. ++ * ++ * @param connection the XMPP connection ++ * @param byteStreamRequest the In-Band Bytestream open request for this session ++ * @param remoteJID JID of the remote peer ++ */ ++ protected InBandBytestreamSession(Connection connection, Open byteStreamRequest, ++ String remoteJID) { ++ this.connection = connection; ++ this.byteStreamRequest = byteStreamRequest; ++ this.remoteJID = remoteJID; ++ ++ // initialize streams dependent to the uses stanza type ++ switch (byteStreamRequest.getStanza()) { ++ case IQ: ++ this.inputStream = new IQIBBInputStream(); ++ this.outputStream = new IQIBBOutputStream(); ++ break; ++ case MESSAGE: ++ this.inputStream = new MessageIBBInputStream(); ++ this.outputStream = new MessageIBBOutputStream(); ++ break; ++ } ++ ++ } ++ ++ public InputStream getInputStream() { ++ return this.inputStream; ++ } ++ ++ public OutputStream getOutputStream() { ++ return this.outputStream; ++ } ++ ++ public int getReadTimeout() { ++ return this.inputStream.readTimeout; ++ } ++ ++ public void setReadTimeout(int timeout) { ++ if (timeout < 0) { ++ throw new IllegalArgumentException("Timeout must be >= 0"); ++ } ++ this.inputStream.readTimeout = timeout; ++ } ++ ++ /** ++ * Returns whether both streams should be closed automatically if one of the streams is closed. ++ * Default is <code>false</code>. ++ * ++ * @return <code>true</code> if both streams will be closed if one of the streams is closed, ++ * <code>false</code> if both streams can be closed independently. ++ */ ++ public boolean isCloseBothStreamsEnabled() { ++ return closeBothStreamsEnabled; ++ } ++ ++ /** ++ * Sets whether both streams should be closed automatically if one of the streams is closed. ++ * Default is <code>false</code>. ++ * ++ * @param closeBothStreamsEnabled <code>true</code> if both streams should be closed if one of ++ * the streams is closed, <code>false</code> if both streams should be closed ++ * independently ++ */ ++ public void setCloseBothStreamsEnabled(boolean closeBothStreamsEnabled) { ++ this.closeBothStreamsEnabled = closeBothStreamsEnabled; ++ } ++ ++ public void close() throws IOException { ++ closeByLocal(true); // close input stream ++ closeByLocal(false); // close output stream ++ } ++ ++ /** ++ * This method is invoked if a request to close the In-Band Bytestream has been received. ++ * ++ * @param closeRequest the close request from the remote peer ++ */ ++ protected void closeByPeer(Close closeRequest) { ++ ++ /* ++ * close streams without flushing them, because stream is already considered closed on the ++ * remote peers side ++ */ ++ this.inputStream.closeInternal(); ++ this.inputStream.cleanup(); ++ this.outputStream.closeInternal(false); ++ ++ // acknowledge close request ++ IQ confirmClose = IQ.createResultIQ(closeRequest); ++ this.connection.sendPacket(confirmClose); ++ ++ } ++ ++ /** ++ * This method is invoked if one of the streams has been closed locally, if an error occurred ++ * locally or if the whole session should be closed. ++ * ++ * @throws IOException if an error occurs while sending the close request ++ */ ++ protected synchronized void closeByLocal(boolean in) throws IOException { ++ if (this.isClosed) { ++ return; ++ } ++ ++ if (this.closeBothStreamsEnabled) { ++ this.inputStream.closeInternal(); ++ this.outputStream.closeInternal(true); ++ } ++ else { ++ if (in) { ++ this.inputStream.closeInternal(); ++ } ++ else { ++ // close stream but try to send any data left ++ this.outputStream.closeInternal(true); ++ } ++ } ++ ++ if (this.inputStream.isClosed && this.outputStream.isClosed) { ++ this.isClosed = true; ++ ++ // send close request ++ Close close = new Close(this.byteStreamRequest.getSessionID()); ++ close.setTo(this.remoteJID); ++ try { ++ SyncPacketSend.getReply(this.connection, close); ++ } ++ catch (XMPPException e) { ++ throw new IOException("Error while closing stream: " + e.getMessage()); ++ } ++ ++ this.inputStream.cleanup(); ++ ++ // remove session from manager ++ InBandBytestreamManager.getByteStreamManager(this.connection).getSessions().remove(this); ++ } ++ ++ } ++ ++ /** ++ * IBBInputStream class is the base implementation of an In-Band Bytestream input stream. ++ * Subclasses of this input stream must provide a packet listener along with a packet filter to ++ * collect the In-Band Bytestream data packets. ++ */ ++ private abstract class IBBInputStream extends InputStream { ++ ++ /* the data packet listener to fill the data queue */ ++ private final PacketListener dataPacketListener; ++ ++ /* queue containing received In-Band Bytestream data packets */ ++ protected final BlockingQueue<DataPacketExtension> dataQueue = new LinkedBlockingQueue<DataPacketExtension>(); ++ ++ /* buffer containing the data from one data packet */ ++ private byte[] buffer; ++ ++ /* pointer to the next byte to read from buffer */ ++ private int bufferPointer = -1; ++ ++ /* data packet sequence (range from 0 to 65535) */ ++ private long seq = -1; ++ ++ /* flag to indicate if input stream is closed */ ++ private boolean isClosed = false; ++ ++ /* flag to indicate if close method was invoked */ ++ private boolean closeInvoked = false; ++ ++ /* timeout for read operations */ ++ private int readTimeout = 0; ++ ++ /** ++ * Constructor. ++ */ ++ public IBBInputStream() { ++ // add data packet listener to connection ++ this.dataPacketListener = getDataPacketListener(); ++ connection.addPacketListener(this.dataPacketListener, getDataPacketFilter()); ++ } ++ ++ /** ++ * Returns the packet listener that processes In-Band Bytestream data packets. ++ * ++ * @return the data packet listener ++ */ ++ protected abstract PacketListener getDataPacketListener(); ++ ++ /** ++ * Returns the packet filter that accepts In-Band Bytestream data packets. ++ * ++ * @return the data packet filter ++ */ ++ protected abstract PacketFilter getDataPacketFilter(); ++ ++ public synchronized int read() throws IOException { ++ checkClosed(); ++ ++ // if nothing read yet or whole buffer has been read fill buffer ++ if (bufferPointer == -1 || bufferPointer >= buffer.length) { ++ // if no data available and stream was closed return -1 ++ if (!loadBuffer()) { ++ return -1; ++ } ++ } ++ ++ // return byte and increment buffer pointer ++ return (int) buffer[bufferPointer++]; ++ } ++ ++ public synchronized int read(byte[] b, int off, int len) throws IOException { ++ if (b == null) { ++ throw new NullPointerException(); ++ } ++ else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) ++ || ((off + len) < 0)) { ++ throw new IndexOutOfBoundsException(); ++ } ++ else if (len == 0) { ++ return 0; ++ } ++ ++ checkClosed(); ++ ++ // if nothing read yet or whole buffer has been read fill buffer ++ if (bufferPointer == -1 || bufferPointer >= buffer.length) { ++ // if no data available and stream was closed return -1 ++ if (!loadBuffer()) { ++ return -1; ++ } ++ } ++ ++ // if more bytes wanted than available return all available ++ int bytesAvailable = buffer.length - bufferPointer; ++ if (len > bytesAvailable) { ++ len = bytesAvailable; ++ } ++ ++ System.arraycopy(buffer, bufferPointer, b, off, len); ++ bufferPointer += len; ++ return len; ++ } ++ ++ public synchronized int read(byte[] b) throws IOException { ++ return read(b, 0, b.length); ++ } ++ ++ /** ++ * This method blocks until a data packet is received, the stream is closed or the current ++ * thread is interrupted. ++ * ++ * @return <code>true</code> if data was received, otherwise <code>false</code> ++ * @throws IOException if data packets are out of sequence ++ */ ++ private synchronized boolean loadBuffer() throws IOException { ++ ++ // wait until data is available or stream is closed ++ DataPacketExtension data = null; ++ try { ++ if (this.readTimeout == 0) { ++ while (data == null) { ++ if (isClosed && this.dataQueue.isEmpty()) { ++ return false; ++ } ++ data = this.dataQueue.poll(1000, TimeUnit.MILLISECONDS); ++ } ++ } ++ else { ++ data = this.dataQueue.poll(this.readTimeout, TimeUnit.MILLISECONDS); ++ if (data == null) { ++ throw new SocketTimeoutException(); ++ } ++ } ++ } ++ catch (InterruptedException e) { ++ // Restore the interrupted status ++ Thread.currentThread().interrupt(); ++ return false; ++ } ++ ++ // handle sequence overflow ++ if (this.seq == 65535) { ++ this.seq = -1; ++ } ++ ++ // check if data packets sequence is successor of last seen sequence ++ long seq = data.getSeq(); ++ if (seq - 1 != this.seq) { ++ // packets out of order; close stream/session ++ InBandBytestreamSession.this.close(); ++ throw new IOException("Packets out of sequence"); ++ } ++ else { ++ this.seq = seq; ++ } ++ ++ // set buffer to decoded data ++ buffer = data.getDecodedData(); ++ bufferPointer = 0; ++ return true; ++ } ++ ++ /** ++ * Checks if this stream is closed and throws an IOException if necessary ++ * ++ * @throws IOException if stream is closed and no data should be read anymore ++ */ ++ private void checkClosed() throws IOException { ++ /* throw no exception if there is data available, but not if close method was invoked */ ++ if ((isClosed && this.dataQueue.isEmpty()) || closeInvoked) { ++ // clear data queue in case additional data was received after stream was closed ++ this.dataQueue.clear(); ++ throw new IOException("Stream is closed"); ++ } ++ } ++ ++ public boolean markSupported() { ++ return false; ++ } ++ ++ public void close() throws IOException { ++ if (isClosed) { ++ return; ++ } ++ ++ this.closeInvoked = true; ++ ++ InBandBytestreamSession.this.closeByLocal(true); ++ } ++ ++ /** ++ * This method sets the close flag and removes the data packet listener. ++ */ ++ private void closeInternal() { ++ if (isClosed) { ++ return; ++ } ++ isClosed = true; ++ } ++ ++ /** ++ * Invoked if the session is closed. ++ */ ++ private void cleanup() { ++ connection.removePacketListener(this.dataPacketListener); ++ } ++ ++ } ++ ++ /** ++ * IQIBBInputStream class implements IBBInputStream to be used with IQ stanzas encapsulating the ++ * data packets. ++ */ ++ private class IQIBBInputStream extends IBBInputStream { ++ ++ protected PacketListener getDataPacketListener() { ++ return new PacketListener() { ++ ++ private long lastSequence = -1; ++ ++ public void processPacket(Packet packet) { ++ // get data packet extension ++ DataPacketExtension data = (DataPacketExtension) packet.getExtension( ++ DataPacketExtension.ELEMENT_NAME, ++ InBandBytestreamManager.NAMESPACE); ++ ++ /* ++ * check if sequence was not used already (see XEP-0047 Section 2.2) ++ */ ++ if (data.getSeq() <= this.lastSequence) { ++ IQ unexpectedRequest = IQ.createErrorResponse((IQ) packet, new XMPPError( ++ XMPPError.Condition.unexpected_request)); ++ connection.sendPacket(unexpectedRequest); ++ return; ++ ++ } ++ ++ // check if encoded data is valid (see XEP-0047 Section 2.2) ++ if (data.getDecodedData() == null) { ++ // data is invalid; respond with bad-request error ++ IQ badRequest = IQ.createErrorResponse((IQ) packet, new XMPPError( ++ XMPPError.Condition.bad_request)); ++ connection.sendPacket(badRequest); ++ return; ++ } ++ ++ // data is valid; add to data queue ++ dataQueue.offer(data); ++ ++ // confirm IQ ++ IQ confirmData = IQ.createResultIQ((IQ) packet); ++ connection.sendPacket(confirmData); ++ ++ // set last seen sequence ++ this.lastSequence = data.getSeq(); ++ if (this.lastSequence == 65535) { ++ this.lastSequence = -1; ++ } ++ ++ } ++ ++ }; ++ } ++ ++ protected PacketFilter getDataPacketFilter() { ++ /* ++ * filter all IQ stanzas having type 'SET' (represented by Data class), containing a ++ * data packet extension, matching session ID and recipient ++ */ ++ return new AndFilter(new PacketTypeFilter(Data.class), new IBBDataPacketFilter()); ++ } ++ ++ } ++ ++ /** ++ * MessageIBBInputStream class implements IBBInputStream to be used with message stanzas ++ * encapsulating the data packets. ++ */ ++ private class MessageIBBInputStream extends IBBInputStream { ++ ++ protected PacketListener getDataPacketListener() { ++ return new PacketListener() { ++ ++ public void processPacket(Packet packet) { ++ // get data packet extension ++ DataPacketExtension data = (DataPacketExtension) packet.getExtension( ++ DataPacketExtension.ELEMENT_NAME, ++ InBandBytestreamManager.NAMESPACE); ++ ++ // check if encoded data is valid ++ if (data.getDecodedData() == null) { ++ /* ++ * TODO once a majority of XMPP server implementation support XEP-0079 ++ * Advanced Message Processing the invalid message could be answered with an ++ * appropriate error. For now we just ignore the packet. Subsequent packets ++ * with an increased sequence will cause the input stream to close the ++ * stream/session. ++ */ ++ return; ++ } ++ ++ // data is valid; add to data queue ++ dataQueue.offer(data); ++ ++ // TODO confirm packet once XMPP servers support XEP-0079 ++ } ++ ++ }; ++ } ++ ++ @Override ++ protected PacketFilter getDataPacketFilter() { ++ /* ++ * filter all message stanzas containing a data packet extension, matching session ID ++ * and recipient ++ */ ++ return new AndFilter(new PacketTypeFilter(Message.class), new IBBDataPacketFilter()); ++ } ++ ++ } ++ ++ /** ++ * IBBDataPacketFilter class filters all packets from the remote peer of this session, ++ * containing an In-Band Bytestream data packet extension whose session ID matches this sessions ++ * ID. ++ */ ++ private class IBBDataPacketFilter implements PacketFilter { ++ ++ public boolean accept(Packet packet) { ++ // sender equals remote peer ++ if (!packet.getFrom().equalsIgnoreCase(remoteJID)) { ++ return false; ++ } ++ ++ // stanza contains data packet extension ++ PacketExtension packetExtension = packet.getExtension(DataPacketExtension.ELEMENT_NAME, ++ InBandBytestreamManager.NAMESPACE); ++ if (packetExtension == null || !(packetExtension instanceof DataPacketExtension)) { ++ return false; ++ } ++ ++ // session ID equals this session ID ++ DataPacketExtension data = (DataPacketExtension) packetExtension; ++ if (!data.getSessionID().equals(byteStreamRequest.getSessionID())) { ++ return false; ++ } ++ ++ return true; ++ } ++ ++ } ++ ++ /** ++ * IBBOutputStream class is the base implementation of an In-Band Bytestream output stream. ++ * Subclasses of this output stream must provide a method to send data over XMPP stream. ++ */ ++ private abstract class IBBOutputStream extends OutputStream { ++ ++ /* buffer with the size of this sessions block size */ ++ protected final byte[] buffer; ++ ++ /* pointer to next byte to write to buffer */ ++ protected int bufferPointer = 0; ++ ++ /* data packet sequence (range from 0 to 65535) */ ++ protected long seq = 0; ++ ++ /* flag to indicate if output stream is closed */ ++ protected boolean isClosed = false; ++ ++ /** ++ * Constructor. ++ */ ++ public IBBOutputStream() { ++ this.buffer = new byte[(byteStreamRequest.getBlockSize()/4)*3]; ++ } ++ ++ /** ++ * Writes the given data packet to the XMPP stream. ++ * ++ * @param data the data packet ++ * @throws IOException if an I/O error occurred while sending or if the stream is closed ++ */ ++ protected abstract void writeToXML(DataPacketExtension data) throws IOException; ++ ++ public synchronized void write(int b) throws IOException { ++ if (this.isClosed) { ++ throw new IOException("Stream is closed"); ++ } ++ ++ // if buffer is full flush buffer ++ if (bufferPointer >= buffer.length) { ++ flushBuffer(); ++ } ++ ++ buffer[bufferPointer++] = (byte) b; ++ } ++ ++ public synchronized void write(byte b[], int off, int len) throws IOException { ++ if (b == null) { ++ throw new NullPointerException(); ++ } ++ else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) ++ || ((off + len) < 0)) { ++ throw new IndexOutOfBoundsException(); ++ } ++ else if (len == 0) { ++ return; ++ } ++ ++ if (this.isClosed) { ++ throw new IOException("Stream is closed"); ++ } ++ ++ // is data to send greater than buffer size ++ if (len >= buffer.length) { ++ ++ // "byte" off the first chunk to write out ++ writeOut(b, off, buffer.length); ++ ++ // recursively call this method with the lesser amount ++ write(b, off + buffer.length, len - buffer.length); ++ } ++ else { ++ writeOut(b, off, len); ++ } ++ } ++ ++ public synchronized void write(byte[] b) throws IOException { ++ write(b, 0, b.length); ++ } ++ ++ /** ++ * Fills the buffer with the given data and sends it over the XMPP stream if the buffers ++ * capacity has been reached. This method is only called from this class so it is assured ++ * that the amount of data to send is <= buffer capacity ++ * ++ * @param b the data ++ * @param off the data ++ * @param len the number of bytes to write ++ * @throws IOException if an I/O error occurred while sending or if the stream is closed ++ */ ++ private synchronized void writeOut(byte b[], int off, int len) throws IOException { ++ if (this.isClosed) { ++ throw new IOException("Stream is closed"); ++ } ++ ++ // set to 0 in case the next 'if' block is not executed ++ int available = 0; ++ ++ // is data to send greater that buffer space left ++ if (len > buffer.length - bufferPointer) { ++ // fill buffer to capacity and send it ++ available = buffer.length - bufferPointer; ++ System.arraycopy(b, off, buffer, bufferPointer, available); ++ bufferPointer += available; ++ flushBuffer(); ++ } ++ ++ // copy the data left to buffer ++ System.arraycopy(b, off + available, buffer, bufferPointer, len - available); ++ bufferPointer += len - available; ++ } ++ ++ public synchronized void flush() throws IOException { ++ if (this.isClosed) { ++ throw new IOException("Stream is closed"); ++ } ++ flushBuffer(); ++ } ++ ++ private synchronized void flushBuffer() throws IOException { ++ ++ // do nothing if no data to send available ++ if (bufferPointer == 0) { ++ return; ++ } ++ ++ // create data packet ++ String enc = StringUtils.encodeBase64(buffer, 0, bufferPointer, false); ++ DataPacketExtension data = new DataPacketExtension(byteStreamRequest.getSessionID(), ++ this.seq, enc); ++ ++ // write to XMPP stream ++ writeToXML(data); ++ ++ // reset buffer pointer ++ bufferPointer = 0; ++ ++ // increment sequence, considering sequence overflow ++ this.seq = (this.seq + 1 == 65535 ? 0 : this.seq + 1); ++ ++ } ++ ++ public void close() throws IOException { ++ if (isClosed) { ++ return; ++ } ++ InBandBytestreamSession.this.closeByLocal(false); ++ } ++ ++ /** ++ * Sets the close flag and optionally flushes the stream. ++ * ++ * @param flush if <code>true</code> flushes the stream ++ */ ++ protected void closeInternal(boolean flush) { ++ if (this.isClosed) { ++ return; ++ } ++ this.isClosed = true; ++ ++ try { ++ if (flush) { ++ flushBuffer(); ++ } ++ } ++ catch (IOException e) { ++ /* ++ * ignore, because writeToXML() will not throw an exception if stream is already ++ * closed ++ */ ++ } ++ } ++ ++ } ++ ++ /** ++ * IQIBBOutputStream class implements IBBOutputStream to be used with IQ stanzas encapsulating ++ * the data packets. ++ */ ++ private class IQIBBOutputStream extends IBBOutputStream { ++ ++ @Override ++ protected synchronized void writeToXML(DataPacketExtension data) throws IOException { ++ // create IQ stanza containing data packet ++ IQ iq = new Data(data); ++ iq.setTo(remoteJID); ++ ++ try { ++ SyncPacketSend.getReply(connection, iq); ++ } ++ catch (XMPPException e) { ++ // close session unless it is already closed ++ if (!this.isClosed) { ++ InBandBytestreamSession.this.close(); ++ throw new IOException("Error while sending Data: " + e.getMessage()); ++ } ++ } ++ ++ } ++ ++ } ++ ++ /** ++ * MessageIBBOutputStream class implements IBBOutputStream to be used with message stanzas ++ * encapsulating the data packets. ++ */ ++ private class MessageIBBOutputStream extends IBBOutputStream { ++ ++ @Override ++ protected synchronized void writeToXML(DataPacketExtension data) { ++ // create message stanza containing data packet ++ Message message = new Message(remoteJID); ++ message.addExtension(data); ++ ++ connection.sendPacket(message); ++ ++ } ++ ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InitiationListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InitiationListener.java +new file mode 100644 +index 0000000..0ecb081 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InitiationListener.java +@@ -0,0 +1,127 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.ibb; ++ ++import java.util.concurrent.ExecutorService; ++import java.util.concurrent.Executors; ++ ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.filter.AndFilter; ++import org.jivesoftware.smack.filter.IQTypeFilter; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smackx.bytestreams.BytestreamListener; ++import org.jivesoftware.smackx.bytestreams.ibb.packet.Open; ++ ++/** ++ * InitiationListener handles all incoming In-Band Bytestream open requests. If there are no ++ * listeners for a In-Band Bytestream request InitiationListener will always refuse the request and ++ * reply with a <not-acceptable/> error (<a ++ * href="http://xmpp.org/extensions/xep-0047.html#example-5" >XEP-0047</a> Section 2.1). ++ * <p> ++ * All In-Band Bytestream request having a block size greater than the maximum allowed block size ++ * for this connection are rejected with an <resource-constraint/> error. The maximum block ++ * size can be set by invoking {@link InBandBytestreamManager#setMaximumBlockSize(int)}. ++ * ++ * @author Henning Staib ++ */ ++class InitiationListener implements PacketListener { ++ ++ /* manager containing the listeners and the XMPP connection */ ++ private final InBandBytestreamManager manager; ++ ++ /* packet filter for all In-Band Bytestream requests */ ++ private final PacketFilter initFilter = new AndFilter(new PacketTypeFilter(Open.class), ++ new IQTypeFilter(IQ.Type.SET)); ++ ++ /* executor service to process incoming requests concurrently */ ++ private final ExecutorService initiationListenerExecutor; ++ ++ /** ++ * Constructor. ++ * ++ * @param manager the In-Band Bytestream manager ++ */ ++ protected InitiationListener(InBandBytestreamManager manager) { ++ this.manager = manager; ++ initiationListenerExecutor = Executors.newCachedThreadPool(); ++ } ++ ++ public void processPacket(final Packet packet) { ++ initiationListenerExecutor.execute(new Runnable() { ++ ++ public void run() { ++ processRequest(packet); ++ } ++ }); ++ } ++ ++ private void processRequest(Packet packet) { ++ Open ibbRequest = (Open) packet; ++ ++ // validate that block size is within allowed range ++ if (ibbRequest.getBlockSize() > this.manager.getMaximumBlockSize()) { ++ this.manager.replyResourceConstraintPacket(ibbRequest); ++ return; ++ } ++ ++ // ignore request if in ignore list ++ if (this.manager.getIgnoredBytestreamRequests().remove(ibbRequest.getSessionID())) ++ return; ++ ++ // build bytestream request from packet ++ InBandBytestreamRequest request = new InBandBytestreamRequest(this.manager, ibbRequest); ++ ++ // notify listeners for bytestream initiation from a specific user ++ BytestreamListener userListener = this.manager.getUserListener(ibbRequest.getFrom()); ++ if (userListener != null) { ++ userListener.incomingBytestreamRequest(request); ++ ++ } ++ else if (!this.manager.getAllRequestListeners().isEmpty()) { ++ /* ++ * if there is no user specific listener inform listeners for all initiation requests ++ */ ++ for (BytestreamListener listener : this.manager.getAllRequestListeners()) { ++ listener.incomingBytestreamRequest(request); ++ } ++ ++ } ++ else { ++ /* ++ * if there is no listener for this initiation request, reply with reject message ++ */ ++ this.manager.replyRejectPacket(ibbRequest); ++ } ++ } ++ ++ /** ++ * Returns the packet filter for In-Band Bytestream open requests. ++ * ++ * @return the packet filter for In-Band Bytestream open requests ++ */ ++ protected PacketFilter getFilter() { ++ return this.initFilter; ++ } ++ ++ /** ++ * Shuts down the listeners executor service. ++ */ ++ protected void shutdown() { ++ this.initiationListenerExecutor.shutdownNow(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/Close.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/Close.java +new file mode 100644 +index 0000000..9a78d73 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/Close.java +@@ -0,0 +1,65 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.ibb.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager; ++ ++/** ++ * Represents a request to close an In-Band Bytestream. ++ * ++ * @author Henning Staib ++ */ ++public class Close extends IQ { ++ ++ /* unique session ID identifying this In-Band Bytestream */ ++ private final String sessionID; ++ ++ /** ++ * Creates a new In-Band Bytestream close request packet. ++ * ++ * @param sessionID unique session ID identifying this In-Band Bytestream ++ */ ++ public Close(String sessionID) { ++ if (sessionID == null || "".equals(sessionID)) { ++ throw new IllegalArgumentException("Session ID must not be null or empty"); ++ } ++ this.sessionID = sessionID; ++ setType(Type.SET); ++ } ++ ++ /** ++ * Returns the unique session ID identifying this In-Band Bytestream. ++ * ++ * @return the unique session ID identifying this In-Band Bytestream ++ */ ++ public String getSessionID() { ++ return sessionID; ++ } ++ ++ @Override ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<close "); ++ buf.append("xmlns=\""); ++ buf.append(InBandBytestreamManager.NAMESPACE); ++ buf.append("\" "); ++ buf.append("sid=\""); ++ buf.append(sessionID); ++ buf.append("\""); ++ buf.append("/>"); ++ return buf.toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/Data.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/Data.java +new file mode 100644 +index 0000000..696fa75 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/Data.java +@@ -0,0 +1,64 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.ibb.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++ ++/** ++ * Represents a chunk of data sent over an In-Band Bytestream encapsulated in an ++ * IQ stanza. ++ * ++ * @author Henning Staib ++ */ ++public class Data extends IQ { ++ ++ /* the data packet extension */ ++ private final DataPacketExtension dataPacketExtension; ++ ++ /** ++ * Constructor. ++ * ++ * @param data data packet extension containing the encoded data ++ */ ++ public Data(DataPacketExtension data) { ++ if (data == null) { ++ throw new IllegalArgumentException("Data must not be null"); ++ } ++ this.dataPacketExtension = data; ++ ++ /* ++ * also set as packet extension so that data packet extension can be ++ * retrieved from IQ stanza and message stanza in the same way ++ */ ++ addExtension(data); ++ setType(IQ.Type.SET); ++ } ++ ++ /** ++ * Returns the data packet extension. ++ * <p> ++ * Convenience method for <code>packet.getExtension("data", ++ * "http://jabber.org/protocol/ibb")</code>. ++ * ++ * @return the data packet extension ++ */ ++ public DataPacketExtension getDataPacketExtension() { ++ return this.dataPacketExtension; ++ } ++ ++ public String getChildElementXML() { ++ return this.dataPacketExtension.toXML(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtension.java +new file mode 100644 +index 0000000..80ed1e1 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtension.java +@@ -0,0 +1,149 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.ibb.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.util.StringUtils; ++import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager; ++ ++/** ++ * Represents a chunk of data of an In-Band Bytestream within an IQ stanza or a ++ * message stanza ++ * ++ * @author Henning Staib ++ */ ++public class DataPacketExtension implements PacketExtension { ++ ++ /** ++ * The element name of the data packet extension. ++ */ ++ public final static String ELEMENT_NAME = "data"; ++ ++ /* unique session ID identifying this In-Band Bytestream */ ++ private final String sessionID; ++ ++ /* sequence of this packet in regard to the other data packets */ ++ private final long seq; ++ ++ /* the data contained in this packet */ ++ private final String data; ++ ++ private byte[] decodedData; ++ ++ /** ++ * Creates a new In-Band Bytestream data packet. ++ * ++ * @param sessionID unique session ID identifying this In-Band Bytestream ++ * @param seq sequence of this packet in regard to the other data packets ++ * @param data the base64 encoded data contained in this packet ++ */ ++ public DataPacketExtension(String sessionID, long seq, String data) { ++ if (sessionID == null || "".equals(sessionID)) { ++ throw new IllegalArgumentException("Session ID must not be null or empty"); ++ } ++ if (seq < 0 || seq > 65535) { ++ throw new IllegalArgumentException("Sequence must not be between 0 and 65535"); ++ } ++ if (data == null) { ++ throw new IllegalArgumentException("Data must not be null"); ++ } ++ this.sessionID = sessionID; ++ this.seq = seq; ++ this.data = data; ++ } ++ ++ /** ++ * Returns the unique session ID identifying this In-Band Bytestream. ++ * ++ * @return the unique session ID identifying this In-Band Bytestream ++ */ ++ public String getSessionID() { ++ return sessionID; ++ } ++ ++ /** ++ * Returns the sequence of this packet in regard to the other data packets. ++ * ++ * @return the sequence of this packet in regard to the other data packets. ++ */ ++ public long getSeq() { ++ return seq; ++ } ++ ++ /** ++ * Returns the data contained in this packet. ++ * ++ * @return the data contained in this packet. ++ */ ++ public String getData() { ++ return data; ++ } ++ ++ /** ++ * Returns the decoded data or null if data could not be decoded. ++ * <p> ++ * The encoded data is invalid if it contains bad Base64 input characters or ++ * if it contains the pad ('=') character on a position other than the last ++ * character(s) of the data. See <a ++ * href="http://xmpp.org/extensions/xep-0047.html#sec">XEP-0047</a> Section ++ * 6. ++ * ++ * @return the decoded data ++ */ ++ public byte[] getDecodedData() { ++ // return cached decoded data ++ if (this.decodedData != null) { ++ return this.decodedData; ++ } ++ ++ // data must not contain the pad (=) other than end of data ++ if (data.matches(".*={1,2}+.+")) { ++ return null; ++ } ++ ++ // decodeBase64 will return null if bad characters are included ++ this.decodedData = StringUtils.decodeBase64(data); ++ return this.decodedData; ++ } ++ ++ public String getElementName() { ++ return ELEMENT_NAME; ++ } ++ ++ public String getNamespace() { ++ return InBandBytestreamManager.NAMESPACE; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<"); ++ buf.append(getElementName()); ++ buf.append(" "); ++ buf.append("xmlns=\""); ++ buf.append(InBandBytestreamManager.NAMESPACE); ++ buf.append("\" "); ++ buf.append("seq=\""); ++ buf.append(seq); ++ buf.append("\" "); ++ buf.append("sid=\""); ++ buf.append(sessionID); ++ buf.append("\">"); ++ buf.append(data); ++ buf.append("</"); ++ buf.append(getElementName()); ++ buf.append(">"); ++ return buf.toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/Open.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/Open.java +new file mode 100644 +index 0000000..94a7a9b +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/Open.java +@@ -0,0 +1,126 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.ibb.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager; ++import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager.StanzaType; ++ ++/** ++ * Represents a request to open an In-Band Bytestream. ++ * ++ * @author Henning Staib ++ */ ++public class Open extends IQ { ++ ++ /* unique session ID identifying this In-Band Bytestream */ ++ private final String sessionID; ++ ++ /* block size in which the data will be fragmented */ ++ private final int blockSize; ++ ++ /* stanza type used to encapsulate the data */ ++ private final StanzaType stanza; ++ ++ /** ++ * Creates a new In-Band Bytestream open request packet. ++ * <p> ++ * The data sent over this In-Band Bytestream will be fragmented in blocks ++ * with the given block size. The block size should not be greater than ++ * 65535. A recommended default value is 4096. ++ * <p> ++ * The data can be sent using IQ stanzas or message stanzas. ++ * ++ * @param sessionID unique session ID identifying this In-Band Bytestream ++ * @param blockSize block size in which the data will be fragmented ++ * @param stanza stanza type used to encapsulate the data ++ */ ++ public Open(String sessionID, int blockSize, StanzaType stanza) { ++ if (sessionID == null || "".equals(sessionID)) { ++ throw new IllegalArgumentException("Session ID must not be null or empty"); ++ } ++ if (blockSize <= 0) { ++ throw new IllegalArgumentException("Block size must be greater than zero"); ++ } ++ ++ this.sessionID = sessionID; ++ this.blockSize = blockSize; ++ this.stanza = stanza; ++ setType(Type.SET); ++ } ++ ++ /** ++ * Creates a new In-Band Bytestream open request packet. ++ * <p> ++ * The data sent over this In-Band Bytestream will be fragmented in blocks ++ * with the given block size. The block size should not be greater than ++ * 65535. A recommended default value is 4096. ++ * <p> ++ * The data will be sent using IQ stanzas. ++ * ++ * @param sessionID unique session ID identifying this In-Band Bytestream ++ * @param blockSize block size in which the data will be fragmented ++ */ ++ public Open(String sessionID, int blockSize) { ++ this(sessionID, blockSize, StanzaType.IQ); ++ } ++ ++ /** ++ * Returns the unique session ID identifying this In-Band Bytestream. ++ * ++ * @return the unique session ID identifying this In-Band Bytestream ++ */ ++ public String getSessionID() { ++ return sessionID; ++ } ++ ++ /** ++ * Returns the block size in which the data will be fragmented. ++ * ++ * @return the block size in which the data will be fragmented ++ */ ++ public int getBlockSize() { ++ return blockSize; ++ } ++ ++ /** ++ * Returns the stanza type used to encapsulate the data. ++ * ++ * @return the stanza type used to encapsulate the data ++ */ ++ public StanzaType getStanza() { ++ return stanza; ++ } ++ ++ @Override ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<open "); ++ buf.append("xmlns=\""); ++ buf.append(InBandBytestreamManager.NAMESPACE); ++ buf.append("\" "); ++ buf.append("block-size=\""); ++ buf.append(blockSize); ++ buf.append("\" "); ++ buf.append("sid=\""); ++ buf.append(sessionID); ++ buf.append("\" "); ++ buf.append("stanza=\""); ++ buf.append(stanza.toString().toLowerCase()); ++ buf.append("\""); ++ buf.append("/>"); ++ return buf.toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/provider/CloseIQProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/provider/CloseIQProvider.java +new file mode 100644 +index 0000000..566724c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/provider/CloseIQProvider.java +@@ -0,0 +1,33 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.ibb.provider; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smackx.bytestreams.ibb.packet.Close; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * Parses a close In-Band Bytestream packet. ++ * ++ * @author Henning Staib ++ */ ++public class CloseIQProvider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ String sid = parser.getAttributeValue("", "sid"); ++ return new Close(sid); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/provider/DataPacketProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/provider/DataPacketProvider.java +new file mode 100644 +index 0000000..5abed08 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/provider/DataPacketProvider.java +@@ -0,0 +1,45 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.ibb.provider; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.jivesoftware.smackx.bytestreams.ibb.packet.Data; ++import org.jivesoftware.smackx.bytestreams.ibb.packet.DataPacketExtension; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * Parses an In-Band Bytestream data packet which can be a packet extension of ++ * either an IQ stanza or a message stanza. ++ * ++ * @author Henning Staib ++ */ ++public class DataPacketProvider implements PacketExtensionProvider, IQProvider { ++ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ String sessionID = parser.getAttributeValue("", "sid"); ++ long seq = Long.parseLong(parser.getAttributeValue("", "seq")); ++ String data = parser.nextText(); ++ return new DataPacketExtension(sessionID, seq, data); ++ } ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ DataPacketExtension data = (DataPacketExtension) parseExtension(parser); ++ IQ iq = new Data(data); ++ return iq; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/provider/OpenIQProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/provider/OpenIQProvider.java +new file mode 100644 +index 0000000..3cc725a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/provider/OpenIQProvider.java +@@ -0,0 +1,45 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.ibb.provider; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager.StanzaType; ++import org.jivesoftware.smackx.bytestreams.ibb.packet.Open; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * Parses an In-Band Bytestream open packet. ++ * ++ * @author Henning Staib ++ */ ++public class OpenIQProvider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ String sessionID = parser.getAttributeValue("", "sid"); ++ int blockSize = Integer.parseInt(parser.getAttributeValue("", "block-size")); ++ ++ String stanzaValue = parser.getAttributeValue("", "stanza"); ++ StanzaType stanza = null; ++ if (stanzaValue == null) { ++ stanza = StanzaType.IQ; ++ } ++ else { ++ stanza = StanzaType.valueOf(stanzaValue.toUpperCase()); ++ } ++ ++ return new Open(sessionID, blockSize, stanza); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/InitiationListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/InitiationListener.java +new file mode 100644 +index 0000000..2a78250 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/InitiationListener.java +@@ -0,0 +1,119 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.socks5; ++ ++import java.util.concurrent.ExecutorService; ++import java.util.concurrent.Executors; ++ ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.filter.AndFilter; ++import org.jivesoftware.smack.filter.IQTypeFilter; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smackx.bytestreams.BytestreamListener; ++import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; ++ ++/** ++ * InitiationListener handles all incoming SOCKS5 Bytestream initiation requests. If there are no ++ * listeners for a SOCKS5 bytestream request InitiationListener will always refuse the request and ++ * reply with a <not-acceptable/> error (<a ++ * href="http://xmpp.org/extensions/xep-0065.html#usecase-alternate">XEP-0065</a> Section 5.2.A2). ++ * ++ * @author Henning Staib ++ */ ++final class InitiationListener implements PacketListener { ++ ++ /* manager containing the listeners and the XMPP connection */ ++ private final Socks5BytestreamManager manager; ++ ++ /* packet filter for all SOCKS5 Bytestream requests */ ++ private final PacketFilter initFilter = new AndFilter(new PacketTypeFilter(Bytestream.class), ++ new IQTypeFilter(IQ.Type.SET)); ++ ++ /* executor service to process incoming requests concurrently */ ++ private final ExecutorService initiationListenerExecutor; ++ ++ /** ++ * Constructor ++ * ++ * @param manager the SOCKS5 Bytestream manager ++ */ ++ protected InitiationListener(Socks5BytestreamManager manager) { ++ this.manager = manager; ++ initiationListenerExecutor = Executors.newCachedThreadPool(); ++ } ++ ++ public void processPacket(final Packet packet) { ++ initiationListenerExecutor.execute(new Runnable() { ++ ++ public void run() { ++ processRequest(packet); ++ } ++ }); ++ } ++ ++ private void processRequest(Packet packet) { ++ Bytestream byteStreamRequest = (Bytestream) packet; ++ ++ // ignore request if in ignore list ++ if (this.manager.getIgnoredBytestreamRequests().remove(byteStreamRequest.getSessionID())) { ++ return; ++ } ++ ++ // build bytestream request from packet ++ Socks5BytestreamRequest request = new Socks5BytestreamRequest(this.manager, ++ byteStreamRequest); ++ ++ // notify listeners for bytestream initiation from a specific user ++ BytestreamListener userListener = this.manager.getUserListener(byteStreamRequest.getFrom()); ++ if (userListener != null) { ++ userListener.incomingBytestreamRequest(request); ++ ++ } ++ else if (!this.manager.getAllRequestListeners().isEmpty()) { ++ /* ++ * if there is no user specific listener inform listeners for all initiation requests ++ */ ++ for (BytestreamListener listener : this.manager.getAllRequestListeners()) { ++ listener.incomingBytestreamRequest(request); ++ } ++ ++ } ++ else { ++ /* ++ * if there is no listener for this initiation request, reply with reject message ++ */ ++ this.manager.replyRejectPacket(byteStreamRequest); ++ } ++ } ++ ++ /** ++ * Returns the packet filter for SOCKS5 Bytestream initialization requests. ++ * ++ * @return the packet filter for SOCKS5 Bytestream initialization requests ++ */ ++ protected PacketFilter getFilter() { ++ return this.initFilter; ++ } ++ ++ /** ++ * Shuts down the listeners executor service. ++ */ ++ protected void shutdown() { ++ this.initiationListenerExecutor.shutdownNow(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamListener.java +new file mode 100644 +index 0000000..1430b1d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamListener.java +@@ -0,0 +1,43 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.socks5; ++ ++import org.jivesoftware.smackx.bytestreams.BytestreamListener; ++import org.jivesoftware.smackx.bytestreams.BytestreamRequest; ++ ++/** ++ * Socks5BytestreamListener are informed if a remote user wants to initiate a SOCKS5 Bytestream. ++ * Implement this interface to handle incoming SOCKS5 Bytestream requests. ++ * <p> ++ * There are two ways to add this listener. See ++ * {@link Socks5BytestreamManager#addIncomingBytestreamListener(BytestreamListener)} and ++ * {@link Socks5BytestreamManager#addIncomingBytestreamListener(BytestreamListener, String)} for ++ * further details. ++ * ++ * @author Henning Staib ++ */ ++public abstract class Socks5BytestreamListener implements BytestreamListener { ++ ++ public void incomingBytestreamRequest(BytestreamRequest request) { ++ incomingBytestreamRequest((Socks5BytestreamRequest) request); ++ } ++ ++ /** ++ * This listener is notified if a SOCKS5 Bytestream request from another user has been received. ++ * ++ * @param request the incoming SOCKS5 Bytestream request ++ */ ++ public abstract void incomingBytestreamRequest(Socks5BytestreamRequest request); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java +new file mode 100644 +index 0000000..41826df +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java +@@ -0,0 +1,760 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.socks5; ++ ++import java.io.IOException; ++import java.net.Socket; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.Iterator; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Map; ++import java.util.Random; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.TimeoutException; ++ ++import org.jivesoftware.smack.AbstractConnectionListener; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.ConnectionCreationListener; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.XMPPError; ++import org.jivesoftware.smack.util.SyncPacketSend; ++import org.jivesoftware.smackx.ServiceDiscoveryManager; ++import org.jivesoftware.smackx.bytestreams.BytestreamListener; ++import org.jivesoftware.smackx.bytestreams.BytestreamManager; ++import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; ++import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost; ++import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHostUsed; ++import org.jivesoftware.smackx.filetransfer.FileTransferManager; ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++import org.jivesoftware.smackx.packet.DiscoverItems; ++import org.jivesoftware.smackx.packet.DiscoverInfo.Identity; ++import org.jivesoftware.smackx.packet.DiscoverItems.Item; ++ ++/** ++ * The Socks5BytestreamManager class handles establishing SOCKS5 Bytestreams as specified in the <a ++ * href="http://xmpp.org/extensions/xep-0065.html">XEP-0065</a>. ++ * <p> ++ * A SOCKS5 Bytestream is negotiated partly over the XMPP XML stream and partly over a separate ++ * socket. The actual transfer though takes place over a separately created socket. ++ * <p> ++ * A SOCKS5 Bytestream generally has three parties, the initiator, the target, and the stream host. ++ * The stream host is a specialized SOCKS5 proxy setup on a server, or, the initiator can act as the ++ * stream host. ++ * <p> ++ * To establish a SOCKS5 Bytestream invoke the {@link #establishSession(String)} method. This will ++ * negotiate a SOCKS5 Bytestream with the given target JID and return a socket. ++ * <p> ++ * If a session ID for the SOCKS5 Bytestream was already negotiated (e.g. while negotiating a file ++ * transfer) invoke {@link #establishSession(String, String)}. ++ * <p> ++ * To handle incoming SOCKS5 Bytestream requests add an {@link Socks5BytestreamListener} to the ++ * manager. There are two ways to add this listener. If you want to be informed about incoming ++ * SOCKS5 Bytestreams from a specific user add the listener by invoking ++ * {@link #addIncomingBytestreamListener(BytestreamListener, String)}. If the listener should ++ * respond to all SOCKS5 Bytestream requests invoke ++ * {@link #addIncomingBytestreamListener(BytestreamListener)}. ++ * <p> ++ * Note that the registered {@link Socks5BytestreamListener} will NOT be notified on incoming Socks5 ++ * bytestream requests sent in the context of <a ++ * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See ++ * {@link FileTransferManager}) ++ * <p> ++ * If no {@link Socks5BytestreamListener}s are registered, all incoming SOCKS5 Bytestream requests ++ * will be rejected by returning a <not-acceptable/> error to the initiator. ++ * ++ * @author Henning Staib ++ */ ++public final class Socks5BytestreamManager implements BytestreamManager { ++ ++ /* ++ * create a new Socks5BytestreamManager and register a shutdown listener on every established ++ * connection ++ */ ++ static { ++ Connection.addConnectionCreationListener(new ConnectionCreationListener() { ++ ++ public void connectionCreated(Connection connection) { ++ final Socks5BytestreamManager manager; ++ manager = Socks5BytestreamManager.getBytestreamManager(connection); ++ ++ // register shutdown listener ++ connection.addConnectionListener(new AbstractConnectionListener() { ++ ++ public void connectionClosed() { ++ manager.disableService(); ++ } ++ ++ }); ++ } ++ ++ }); ++ } ++ ++ /** ++ * The XMPP namespace of the SOCKS5 Bytestream ++ */ ++ public static final String NAMESPACE = "http://jabber.org/protocol/bytestreams"; ++ ++ /* prefix used to generate session IDs */ ++ private static final String SESSION_ID_PREFIX = "js5_"; ++ ++ /* random generator to create session IDs */ ++ private final static Random randomGenerator = new Random(); ++ ++ /* stores one Socks5BytestreamManager for each XMPP connection */ ++ private final static Map<Connection, Socks5BytestreamManager> managers = new HashMap<Connection, Socks5BytestreamManager>(); ++ ++ /* XMPP connection */ ++ private final Connection connection; ++ ++ /* ++ * assigns a user to a listener that is informed if a bytestream request for this user is ++ * received ++ */ ++ private final Map<String, BytestreamListener> userListeners = new ConcurrentHashMap<String, BytestreamListener>(); ++ ++ /* ++ * list of listeners that respond to all bytestream requests if there are not user specific ++ * listeners for that request ++ */ ++ private final List<BytestreamListener> allRequestListeners = Collections.synchronizedList(new LinkedList<BytestreamListener>()); ++ ++ /* listener that handles all incoming bytestream requests */ ++ private final InitiationListener initiationListener; ++ ++ /* timeout to wait for the response to the SOCKS5 Bytestream initialization request */ ++ private int targetResponseTimeout = 10000; ++ ++ /* timeout for connecting to the SOCKS5 proxy selected by the target */ ++ private int proxyConnectionTimeout = 10000; ++ ++ /* blacklist of errornous SOCKS5 proxies */ ++ private final List<String> proxyBlacklist = Collections.synchronizedList(new LinkedList<String>()); ++ ++ /* remember the last proxy that worked to prioritize it */ ++ private String lastWorkingProxy = null; ++ ++ /* flag to enable/disable prioritization of last working proxy */ ++ private boolean proxyPrioritizationEnabled = true; ++ ++ /* ++ * list containing session IDs of SOCKS5 Bytestream initialization packets that should be ++ * ignored by the InitiationListener ++ */ ++ private List<String> ignoredBytestreamRequests = Collections.synchronizedList(new LinkedList<String>()); ++ ++ /** ++ * Returns the Socks5BytestreamManager to handle SOCKS5 Bytestreams for a given ++ * {@link Connection}. ++ * <p> ++ * If no manager exists a new is created and initialized. ++ * ++ * @param connection the XMPP connection or <code>null</code> if given connection is ++ * <code>null</code> ++ * @return the Socks5BytestreamManager for the given XMPP connection ++ */ ++ public static synchronized Socks5BytestreamManager getBytestreamManager(Connection connection) { ++ if (connection == null) { ++ return null; ++ } ++ Socks5BytestreamManager manager = managers.get(connection); ++ if (manager == null) { ++ manager = new Socks5BytestreamManager(connection); ++ managers.put(connection, manager); ++ manager.activate(); ++ } ++ return manager; ++ } ++ ++ /** ++ * Private constructor. ++ * ++ * @param connection the XMPP connection ++ */ ++ private Socks5BytestreamManager(Connection connection) { ++ this.connection = connection; ++ this.initiationListener = new InitiationListener(this); ++ } ++ ++ /** ++ * Adds BytestreamListener that is called for every incoming SOCKS5 Bytestream request unless ++ * there is a user specific BytestreamListener registered. ++ * <p> ++ * If no listeners are registered all SOCKS5 Bytestream request are rejected with a ++ * <not-acceptable/> error. ++ * <p> ++ * Note that the registered {@link BytestreamListener} will NOT be notified on incoming Socks5 ++ * bytestream requests sent in the context of <a ++ * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See ++ * {@link FileTransferManager}) ++ * ++ * @param listener the listener to register ++ */ ++ public void addIncomingBytestreamListener(BytestreamListener listener) { ++ this.allRequestListeners.add(listener); ++ } ++ ++ /** ++ * Removes the given listener from the list of listeners for all incoming SOCKS5 Bytestream ++ * requests. ++ * ++ * @param listener the listener to remove ++ */ ++ public void removeIncomingBytestreamListener(BytestreamListener listener) { ++ this.allRequestListeners.remove(listener); ++ } ++ ++ /** ++ * Adds BytestreamListener that is called for every incoming SOCKS5 Bytestream request from the ++ * given user. ++ * <p> ++ * Use this method if you are awaiting an incoming SOCKS5 Bytestream request from a specific ++ * user. ++ * <p> ++ * If no listeners are registered all SOCKS5 Bytestream request are rejected with a ++ * <not-acceptable/> error. ++ * <p> ++ * Note that the registered {@link BytestreamListener} will NOT be notified on incoming Socks5 ++ * bytestream requests sent in the context of <a ++ * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See ++ * {@link FileTransferManager}) ++ * ++ * @param listener the listener to register ++ * @param initiatorJID the JID of the user that wants to establish a SOCKS5 Bytestream ++ */ ++ public void addIncomingBytestreamListener(BytestreamListener listener, String initiatorJID) { ++ this.userListeners.put(initiatorJID, listener); ++ } ++ ++ /** ++ * Removes the listener for the given user. ++ * ++ * @param initiatorJID the JID of the user the listener should be removed ++ */ ++ public void removeIncomingBytestreamListener(String initiatorJID) { ++ this.userListeners.remove(initiatorJID); ++ } ++ ++ /** ++ * Use this method to ignore the next incoming SOCKS5 Bytestream request containing the given ++ * session ID. No listeners will be notified for this request and and no error will be returned ++ * to the initiator. ++ * <p> ++ * This method should be used if you are awaiting a SOCKS5 Bytestream request as a reply to ++ * another packet (e.g. file transfer). ++ * ++ * @param sessionID to be ignored ++ */ ++ public void ignoreBytestreamRequestOnce(String sessionID) { ++ this.ignoredBytestreamRequests.add(sessionID); ++ } ++ ++ /** ++ * Disables the SOCKS5 Bytestream manager by removing the SOCKS5 Bytestream feature from the ++ * service discovery, disabling the listener for SOCKS5 Bytestream initiation requests and ++ * resetting its internal state. ++ * <p> ++ * To re-enable the SOCKS5 Bytestream feature invoke {@link #getBytestreamManager(Connection)}. ++ * Using the file transfer API will automatically re-enable the SOCKS5 Bytestream feature. ++ */ ++ public synchronized void disableService() { ++ ++ // remove initiation packet listener ++ this.connection.removePacketListener(this.initiationListener); ++ ++ // shutdown threads ++ this.initiationListener.shutdown(); ++ ++ // clear listeners ++ this.allRequestListeners.clear(); ++ this.userListeners.clear(); ++ ++ // reset internal state ++ this.lastWorkingProxy = null; ++ this.proxyBlacklist.clear(); ++ this.ignoredBytestreamRequests.clear(); ++ ++ // remove manager from static managers map ++ managers.remove(this.connection); ++ ++ // shutdown local SOCKS5 proxy if there are no more managers for other connections ++ if (managers.size() == 0) { ++ Socks5Proxy.getSocks5Proxy().stop(); ++ } ++ ++ // remove feature from service discovery ++ ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection); ++ ++ // check if service discovery is not already disposed by connection shutdown ++ if (serviceDiscoveryManager != null) { ++ serviceDiscoveryManager.removeFeature(NAMESPACE); ++ } ++ ++ } ++ ++ /** ++ * Returns the timeout to wait for the response to the SOCKS5 Bytestream initialization request. ++ * Default is 10000ms. ++ * ++ * @return the timeout to wait for the response to the SOCKS5 Bytestream initialization request ++ */ ++ public int getTargetResponseTimeout() { ++ if (this.targetResponseTimeout <= 0) { ++ this.targetResponseTimeout = 10000; ++ } ++ return targetResponseTimeout; ++ } ++ ++ /** ++ * Sets the timeout to wait for the response to the SOCKS5 Bytestream initialization request. ++ * Default is 10000ms. ++ * ++ * @param targetResponseTimeout the timeout to set ++ */ ++ public void setTargetResponseTimeout(int targetResponseTimeout) { ++ this.targetResponseTimeout = targetResponseTimeout; ++ } ++ ++ /** ++ * Returns the timeout for connecting to the SOCKS5 proxy selected by the target. Default is ++ * 10000ms. ++ * ++ * @return the timeout for connecting to the SOCKS5 proxy selected by the target ++ */ ++ public int getProxyConnectionTimeout() { ++ if (this.proxyConnectionTimeout <= 0) { ++ this.proxyConnectionTimeout = 10000; ++ } ++ return proxyConnectionTimeout; ++ } ++ ++ /** ++ * Sets the timeout for connecting to the SOCKS5 proxy selected by the target. Default is ++ * 10000ms. ++ * ++ * @param proxyConnectionTimeout the timeout to set ++ */ ++ public void setProxyConnectionTimeout(int proxyConnectionTimeout) { ++ this.proxyConnectionTimeout = proxyConnectionTimeout; ++ } ++ ++ /** ++ * Returns if the prioritization of the last working SOCKS5 proxy on successive SOCKS5 ++ * Bytestream connections is enabled. Default is <code>true</code>. ++ * ++ * @return <code>true</code> if prioritization is enabled, <code>false</code> otherwise ++ */ ++ public boolean isProxyPrioritizationEnabled() { ++ return proxyPrioritizationEnabled; ++ } ++ ++ /** ++ * Enable/disable the prioritization of the last working SOCKS5 proxy on successive SOCKS5 ++ * Bytestream connections. ++ * ++ * @param proxyPrioritizationEnabled enable/disable the prioritization of the last working ++ * SOCKS5 proxy ++ */ ++ public void setProxyPrioritizationEnabled(boolean proxyPrioritizationEnabled) { ++ this.proxyPrioritizationEnabled = proxyPrioritizationEnabled; ++ } ++ ++ /** ++ * Establishes a SOCKS5 Bytestream with the given user and returns the Socket to send/receive ++ * data to/from the user. ++ * <p> ++ * Use this method to establish SOCKS5 Bytestreams to users accepting all incoming Socks5 ++ * bytestream requests since this method doesn't provide a way to tell the user something about ++ * the data to be sent. ++ * <p> ++ * To establish a SOCKS5 Bytestream after negotiation the kind of data to be sent (e.g. file ++ * transfer) use {@link #establishSession(String, String)}. ++ * ++ * @param targetJID the JID of the user a SOCKS5 Bytestream should be established ++ * @return the Socket to send/receive data to/from the user ++ * @throws XMPPException if the user doesn't support or accept SOCKS5 Bytestreams, if no Socks5 ++ * Proxy could be found, if the user couldn't connect to any of the SOCKS5 Proxies ++ * @throws IOException if the bytestream could not be established ++ * @throws InterruptedException if the current thread was interrupted while waiting ++ */ ++ public Socks5BytestreamSession establishSession(String targetJID) throws XMPPException, ++ IOException, InterruptedException { ++ String sessionID = getNextSessionID(); ++ return establishSession(targetJID, sessionID); ++ } ++ ++ /** ++ * Establishes a SOCKS5 Bytestream with the given user using the given session ID and returns ++ * the Socket to send/receive data to/from the user. ++ * ++ * @param targetJID the JID of the user a SOCKS5 Bytestream should be established ++ * @param sessionID the session ID for the SOCKS5 Bytestream request ++ * @return the Socket to send/receive data to/from the user ++ * @throws XMPPException if the user doesn't support or accept SOCKS5 Bytestreams, if no Socks5 ++ * Proxy could be found, if the user couldn't connect to any of the SOCKS5 Proxies ++ * @throws IOException if the bytestream could not be established ++ * @throws InterruptedException if the current thread was interrupted while waiting ++ */ ++ public Socks5BytestreamSession establishSession(String targetJID, String sessionID) ++ throws XMPPException, IOException, InterruptedException { ++ ++ // check if target supports SOCKS5 Bytestream ++ if (!supportsSocks5(targetJID)) { ++ throw new XMPPException(targetJID + " doesn't support SOCKS5 Bytestream"); ++ } ++ ++ // determine SOCKS5 proxies from XMPP-server ++ List<String> proxies = determineProxies(); ++ ++ // determine address and port of each proxy ++ List<StreamHost> streamHosts = determineStreamHostInfos(proxies); ++ ++ // compute digest ++ String digest = Socks5Utils.createDigest(sessionID, this.connection.getUser(), targetJID); ++ ++ if (streamHosts.isEmpty()) { ++ throw new XMPPException("no SOCKS5 proxies available"); ++ } ++ ++ // prioritize last working SOCKS5 proxy if exists ++ if (this.proxyPrioritizationEnabled && this.lastWorkingProxy != null) { ++ StreamHost selectedStreamHost = null; ++ for (StreamHost streamHost : streamHosts) { ++ if (streamHost.getJID().equals(this.lastWorkingProxy)) { ++ selectedStreamHost = streamHost; ++ break; ++ } ++ } ++ if (selectedStreamHost != null) { ++ streamHosts.remove(selectedStreamHost); ++ streamHosts.add(0, selectedStreamHost); ++ } ++ ++ } ++ ++ Socks5Proxy socks5Proxy = Socks5Proxy.getSocks5Proxy(); ++ try { ++ ++ // add transfer digest to local proxy to make transfer valid ++ socks5Proxy.addTransfer(digest); ++ ++ // create initiation packet ++ Bytestream initiation = createBytestreamInitiation(sessionID, targetJID, streamHosts); ++ ++ // send initiation packet ++ Packet response = SyncPacketSend.getReply(this.connection, initiation, ++ getTargetResponseTimeout()); ++ ++ // extract used stream host from response ++ StreamHostUsed streamHostUsed = ((Bytestream) response).getUsedHost(); ++ StreamHost usedStreamHost = initiation.getStreamHost(streamHostUsed.getJID()); ++ ++ if (usedStreamHost == null) { ++ throw new XMPPException("Remote user responded with unknown host"); ++ } ++ ++ // build SOCKS5 client ++ Socks5Client socks5Client = new Socks5ClientForInitiator(usedStreamHost, digest, ++ this.connection, sessionID, targetJID); ++ ++ // establish connection to proxy ++ Socket socket = socks5Client.getSocket(getProxyConnectionTimeout()); ++ ++ // remember last working SOCKS5 proxy to prioritize it for next request ++ this.lastWorkingProxy = usedStreamHost.getJID(); ++ ++ // negotiation successful, return the output stream ++ return new Socks5BytestreamSession(socket, usedStreamHost.getJID().equals( ++ this.connection.getUser())); ++ ++ } ++ catch (TimeoutException e) { ++ throw new IOException("Timeout while connecting to SOCKS5 proxy"); ++ } ++ finally { ++ ++ // remove transfer digest if output stream is returned or an exception ++ // occurred ++ socks5Proxy.removeTransfer(digest); ++ ++ } ++ } ++ ++ /** ++ * Returns <code>true</code> if the given target JID supports feature SOCKS5 Bytestream. ++ * ++ * @param targetJID the target JID ++ * @return <code>true</code> if the given target JID supports feature SOCKS5 Bytestream ++ * otherwise <code>false</code> ++ * @throws XMPPException if there was an error querying target for supported features ++ */ ++ private boolean supportsSocks5(String targetJID) throws XMPPException { ++ ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection); ++ DiscoverInfo discoverInfo = serviceDiscoveryManager.discoverInfo(targetJID); ++ return discoverInfo.containsFeature(NAMESPACE); ++ } ++ ++ /** ++ * Returns a list of JIDs of SOCKS5 proxies by querying the XMPP server. The SOCKS5 proxies are ++ * in the same order as returned by the XMPP server. ++ * ++ * @return list of JIDs of SOCKS5 proxies ++ * @throws XMPPException if there was an error querying the XMPP server for SOCKS5 proxies ++ */ ++ private List<String> determineProxies() throws XMPPException { ++ ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection); ++ ++ List<String> proxies = new ArrayList<String>(); ++ ++ // get all items form XMPP server ++ DiscoverItems discoverItems = serviceDiscoveryManager.discoverItems(this.connection.getServiceName()); ++ Iterator<Item> itemIterator = discoverItems.getItems(); ++ ++ // query all items if they are SOCKS5 proxies ++ while (itemIterator.hasNext()) { ++ Item item = itemIterator.next(); ++ ++ // skip blacklisted servers ++ if (this.proxyBlacklist.contains(item.getEntityID())) { ++ continue; ++ } ++ ++ try { ++ DiscoverInfo proxyInfo; ++ proxyInfo = serviceDiscoveryManager.discoverInfo(item.getEntityID()); ++ Iterator<Identity> identities = proxyInfo.getIdentities(); ++ ++ // item must have category "proxy" and type "bytestream" ++ while (identities.hasNext()) { ++ Identity identity = identities.next(); ++ ++ if ("proxy".equalsIgnoreCase(identity.getCategory()) ++ && "bytestreams".equalsIgnoreCase(identity.getType())) { ++ proxies.add(item.getEntityID()); ++ break; ++ } ++ ++ /* ++ * server is not a SOCKS5 proxy, blacklist server to skip next time a Socks5 ++ * bytestream should be established ++ */ ++ this.proxyBlacklist.add(item.getEntityID()); ++ ++ } ++ } ++ catch (XMPPException e) { ++ // blacklist errornous server ++ this.proxyBlacklist.add(item.getEntityID()); ++ } ++ } ++ ++ return proxies; ++ } ++ ++ /** ++ * Returns a list of stream hosts containing the IP address an the port for the given list of ++ * SOCKS5 proxy JIDs. The order of the returned list is the same as the given list of JIDs ++ * excluding all SOCKS5 proxies who's network settings could not be determined. If a local ++ * SOCKS5 proxy is running it will be the first item in the list returned. ++ * ++ * @param proxies a list of SOCKS5 proxy JIDs ++ * @return a list of stream hosts containing the IP address an the port ++ */ ++ private List<StreamHost> determineStreamHostInfos(List<String> proxies) { ++ List<StreamHost> streamHosts = new ArrayList<StreamHost>(); ++ ++ // add local proxy on first position if exists ++ List<StreamHost> localProxies = getLocalStreamHost(); ++ if (localProxies != null) { ++ streamHosts.addAll(localProxies); ++ } ++ ++ // query SOCKS5 proxies for network settings ++ for (String proxy : proxies) { ++ Bytestream streamHostRequest = createStreamHostRequest(proxy); ++ try { ++ Bytestream response = (Bytestream) SyncPacketSend.getReply(this.connection, ++ streamHostRequest); ++ streamHosts.addAll(response.getStreamHosts()); ++ } ++ catch (XMPPException e) { ++ // blacklist errornous proxies ++ this.proxyBlacklist.add(proxy); ++ } ++ } ++ ++ return streamHosts; ++ } ++ ++ /** ++ * Returns a IQ packet to query a SOCKS5 proxy its network settings. ++ * ++ * @param proxy the proxy to query ++ * @return IQ packet to query a SOCKS5 proxy its network settings ++ */ ++ private Bytestream createStreamHostRequest(String proxy) { ++ Bytestream request = new Bytestream(); ++ request.setType(IQ.Type.GET); ++ request.setTo(proxy); ++ return request; ++ } ++ ++ /** ++ * Returns the stream host information of the local SOCKS5 proxy containing the IP address and ++ * the port or null if local SOCKS5 proxy is not running. ++ * ++ * @return the stream host information of the local SOCKS5 proxy or null if local SOCKS5 proxy ++ * is not running ++ */ ++ private List<StreamHost> getLocalStreamHost() { ++ ++ // get local proxy singleton ++ Socks5Proxy socks5Server = Socks5Proxy.getSocks5Proxy(); ++ ++ if (socks5Server.isRunning()) { ++ List<String> addresses = socks5Server.getLocalAddresses(); ++ int port = socks5Server.getPort(); ++ ++ if (addresses.size() >= 1) { ++ List<StreamHost> streamHosts = new ArrayList<StreamHost>(); ++ for (String address : addresses) { ++ StreamHost streamHost = new StreamHost(this.connection.getUser(), address); ++ streamHost.setPort(port); ++ streamHosts.add(streamHost); ++ } ++ return streamHosts; ++ } ++ ++ } ++ ++ // server is not running or local address could not be determined ++ return null; ++ } ++ ++ /** ++ * Returns a SOCKS5 Bytestream initialization request packet with the given session ID ++ * containing the given stream hosts for the given target JID. ++ * ++ * @param sessionID the session ID for the SOCKS5 Bytestream ++ * @param targetJID the target JID of SOCKS5 Bytestream request ++ * @param streamHosts a list of SOCKS5 proxies the target should connect to ++ * @return a SOCKS5 Bytestream initialization request packet ++ */ ++ private Bytestream createBytestreamInitiation(String sessionID, String targetJID, ++ List<StreamHost> streamHosts) { ++ Bytestream initiation = new Bytestream(sessionID); ++ ++ // add all stream hosts ++ for (StreamHost streamHost : streamHosts) { ++ initiation.addStreamHost(streamHost); ++ } ++ ++ initiation.setType(IQ.Type.SET); ++ initiation.setTo(targetJID); ++ ++ return initiation; ++ } ++ ++ /** ++ * Responses to the given packet's sender with a XMPP error that a SOCKS5 Bytestream is not ++ * accepted. ++ * ++ * @param packet Packet that should be answered with a not-acceptable error ++ */ ++ protected void replyRejectPacket(IQ packet) { ++ XMPPError xmppError = new XMPPError(XMPPError.Condition.no_acceptable); ++ IQ errorIQ = IQ.createErrorResponse(packet, xmppError); ++ this.connection.sendPacket(errorIQ); ++ } ++ ++ /** ++ * Activates the Socks5BytestreamManager by registering the SOCKS5 Bytestream initialization ++ * listener and enabling the SOCKS5 Bytestream feature. ++ */ ++ private void activate() { ++ // register bytestream initiation packet listener ++ this.connection.addPacketListener(this.initiationListener, ++ this.initiationListener.getFilter()); ++ ++ // enable SOCKS5 feature ++ enableService(); ++ } ++ ++ /** ++ * Adds the SOCKS5 Bytestream feature to the service discovery. ++ */ ++ private void enableService() { ++ ServiceDiscoveryManager manager = ServiceDiscoveryManager.getInstanceFor(this.connection); ++ if (!manager.includesFeature(NAMESPACE)) { ++ manager.addFeature(NAMESPACE); ++ } ++ } ++ ++ /** ++ * Returns a new unique session ID. ++ * ++ * @return a new unique session ID ++ */ ++ private String getNextSessionID() { ++ StringBuilder buffer = new StringBuilder(); ++ buffer.append(SESSION_ID_PREFIX); ++ buffer.append(Math.abs(randomGenerator.nextLong())); ++ return buffer.toString(); ++ } ++ ++ /** ++ * Returns the XMPP connection. ++ * ++ * @return the XMPP connection ++ */ ++ protected Connection getConnection() { ++ return this.connection; ++ } ++ ++ /** ++ * Returns the {@link BytestreamListener} that should be informed if a SOCKS5 Bytestream request ++ * from the given initiator JID is received. ++ * ++ * @param initiator the initiator's JID ++ * @return the listener ++ */ ++ protected BytestreamListener getUserListener(String initiator) { ++ return this.userListeners.get(initiator); ++ } ++ ++ /** ++ * Returns a list of {@link BytestreamListener} that are informed if there are no listeners for ++ * a specific initiator. ++ * ++ * @return list of listeners ++ */ ++ protected List<BytestreamListener> getAllRequestListeners() { ++ return this.allRequestListeners; ++ } ++ ++ /** ++ * Returns the list of session IDs that should be ignored by the InitialtionListener ++ * ++ * @return list of session IDs ++ */ ++ protected List<String> getIgnoredBytestreamRequests() { ++ return ignoredBytestreamRequests; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamRequest.java +new file mode 100644 +index 0000000..0b2fdeb +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamRequest.java +@@ -0,0 +1,316 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.socks5; ++ ++import java.io.IOException; ++import java.net.Socket; ++import java.util.Collection; ++import java.util.concurrent.TimeoutException; ++ ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.XMPPError; ++import org.jivesoftware.smack.util.Cache; ++import org.jivesoftware.smackx.bytestreams.BytestreamRequest; ++import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; ++import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost; ++ ++/** ++ * Socks5BytestreamRequest class handles incoming SOCKS5 Bytestream requests. ++ * ++ * @author Henning Staib ++ */ ++public class Socks5BytestreamRequest implements BytestreamRequest { ++ ++ /* lifetime of an Item in the blacklist */ ++ private static final long BLACKLIST_LIFETIME = 60 * 1000 * 120; ++ ++ /* size of the blacklist */ ++ private static final int BLACKLIST_MAX_SIZE = 100; ++ ++ /* blacklist of addresses of SOCKS5 proxies */ ++ private static final Cache<String, Integer> ADDRESS_BLACKLIST = new Cache<String, Integer>( ++ BLACKLIST_MAX_SIZE, BLACKLIST_LIFETIME); ++ ++ /* ++ * The number of connection failures it takes for a particular SOCKS5 proxy to be blacklisted. ++ * When a proxy is blacklisted no more connection attempts will be made to it for a period of 2 ++ * hours. ++ */ ++ private static int CONNECTION_FAILURE_THRESHOLD = 2; ++ ++ /* the bytestream initialization request */ ++ private Bytestream bytestreamRequest; ++ ++ /* SOCKS5 Bytestream manager containing the XMPP connection and helper methods */ ++ private Socks5BytestreamManager manager; ++ ++ /* timeout to connect to all SOCKS5 proxies */ ++ private int totalConnectTimeout = 10000; ++ ++ /* minimum timeout to connect to one SOCKS5 proxy */ ++ private int minimumConnectTimeout = 2000; ++ ++ /** ++ * Returns the number of connection failures it takes for a particular SOCKS5 proxy to be ++ * blacklisted. When a proxy is blacklisted no more connection attempts will be made to it for a ++ * period of 2 hours. Default is 2. ++ * ++ * @return the number of connection failures it takes for a particular SOCKS5 proxy to be ++ * blacklisted ++ */ ++ public static int getConnectFailureThreshold() { ++ return CONNECTION_FAILURE_THRESHOLD; ++ } ++ ++ /** ++ * Sets the number of connection failures it takes for a particular SOCKS5 proxy to be ++ * blacklisted. When a proxy is blacklisted no more connection attempts will be made to it for a ++ * period of 2 hours. Default is 2. ++ * <p> ++ * Setting the connection failure threshold to zero disables the blacklisting. ++ * ++ * @param connectFailureThreshold the number of connection failures it takes for a particular ++ * SOCKS5 proxy to be blacklisted ++ */ ++ public static void setConnectFailureThreshold(int connectFailureThreshold) { ++ CONNECTION_FAILURE_THRESHOLD = connectFailureThreshold; ++ } ++ ++ /** ++ * Creates a new Socks5BytestreamRequest. ++ * ++ * @param manager the SOCKS5 Bytestream manager ++ * @param bytestreamRequest the SOCKS5 Bytestream initialization packet ++ */ ++ protected Socks5BytestreamRequest(Socks5BytestreamManager manager, Bytestream bytestreamRequest) { ++ this.manager = manager; ++ this.bytestreamRequest = bytestreamRequest; ++ } ++ ++ /** ++ * Returns the maximum timeout to connect to SOCKS5 proxies. Default is 10000ms. ++ * <p> ++ * When accepting a SOCKS5 Bytestream request Smack tries to connect to all SOCKS5 proxies given ++ * by the initiator until a connection is established. This timeout divided by the number of ++ * SOCKS5 proxies determines the timeout for every connection attempt. ++ * <p> ++ * You can set the minimum timeout for establishing a connection to one SOCKS5 proxy by invoking ++ * {@link #setMinimumConnectTimeout(int)}. ++ * ++ * @return the maximum timeout to connect to SOCKS5 proxies ++ */ ++ public int getTotalConnectTimeout() { ++ if (this.totalConnectTimeout <= 0) { ++ return 10000; ++ } ++ return this.totalConnectTimeout; ++ } ++ ++ /** ++ * Sets the maximum timeout to connect to SOCKS5 proxies. Default is 10000ms. ++ * <p> ++ * When accepting a SOCKS5 Bytestream request Smack tries to connect to all SOCKS5 proxies given ++ * by the initiator until a connection is established. This timeout divided by the number of ++ * SOCKS5 proxies determines the timeout for every connection attempt. ++ * <p> ++ * You can set the minimum timeout for establishing a connection to one SOCKS5 proxy by invoking ++ * {@link #setMinimumConnectTimeout(int)}. ++ * ++ * @param totalConnectTimeout the maximum timeout to connect to SOCKS5 proxies ++ */ ++ public void setTotalConnectTimeout(int totalConnectTimeout) { ++ this.totalConnectTimeout = totalConnectTimeout; ++ } ++ ++ /** ++ * Returns the timeout to connect to one SOCKS5 proxy while accepting the SOCKS5 Bytestream ++ * request. Default is 2000ms. ++ * ++ * @return the timeout to connect to one SOCKS5 proxy ++ */ ++ public int getMinimumConnectTimeout() { ++ if (this.minimumConnectTimeout <= 0) { ++ return 2000; ++ } ++ return this.minimumConnectTimeout; ++ } ++ ++ /** ++ * Sets the timeout to connect to one SOCKS5 proxy while accepting the SOCKS5 Bytestream ++ * request. Default is 2000ms. ++ * ++ * @param minimumConnectTimeout the timeout to connect to one SOCKS5 proxy ++ */ ++ public void setMinimumConnectTimeout(int minimumConnectTimeout) { ++ this.minimumConnectTimeout = minimumConnectTimeout; ++ } ++ ++ /** ++ * Returns the sender of the SOCKS5 Bytestream initialization request. ++ * ++ * @return the sender of the SOCKS5 Bytestream initialization request. ++ */ ++ public String getFrom() { ++ return this.bytestreamRequest.getFrom(); ++ } ++ ++ /** ++ * Returns the session ID of the SOCKS5 Bytestream initialization request. ++ * ++ * @return the session ID of the SOCKS5 Bytestream initialization request. ++ */ ++ public String getSessionID() { ++ return this.bytestreamRequest.getSessionID(); ++ } ++ ++ /** ++ * Accepts the SOCKS5 Bytestream initialization request and returns the socket to send/receive ++ * data. ++ * <p> ++ * Before accepting the SOCKS5 Bytestream request you can set timeouts by invoking ++ * {@link #setTotalConnectTimeout(int)} and {@link #setMinimumConnectTimeout(int)}. ++ * ++ * @return the socket to send/receive data ++ * @throws XMPPException if connection to all SOCKS5 proxies failed or if stream is invalid. ++ * @throws InterruptedException if the current thread was interrupted while waiting ++ */ ++ public Socks5BytestreamSession accept() throws XMPPException, InterruptedException { ++ Collection<StreamHost> streamHosts = this.bytestreamRequest.getStreamHosts(); ++ ++ // throw exceptions if request contains no stream hosts ++ if (streamHosts.size() == 0) { ++ cancelRequest(); ++ } ++ ++ StreamHost selectedHost = null; ++ Socket socket = null; ++ ++ String digest = Socks5Utils.createDigest(this.bytestreamRequest.getSessionID(), ++ this.bytestreamRequest.getFrom(), this.manager.getConnection().getUser()); ++ ++ /* ++ * determine timeout for each connection attempt; each SOCKS5 proxy has the same amount of ++ * time so that the first does not consume the whole timeout ++ */ ++ int timeout = Math.max(getTotalConnectTimeout() / streamHosts.size(), ++ getMinimumConnectTimeout()); ++ ++ for (StreamHost streamHost : streamHosts) { ++ String address = streamHost.getAddress() + ":" + streamHost.getPort(); ++ ++ // check to see if this address has been blacklisted ++ int failures = getConnectionFailures(address); ++ if (CONNECTION_FAILURE_THRESHOLD > 0 && failures >= CONNECTION_FAILURE_THRESHOLD) { ++ continue; ++ } ++ ++ // establish socket ++ try { ++ ++ // build SOCKS5 client ++ final Socks5Client socks5Client = new Socks5Client(streamHost, digest); ++ ++ // connect to SOCKS5 proxy with a timeout ++ socket = socks5Client.getSocket(timeout); ++ ++ // set selected host ++ selectedHost = streamHost; ++ break; ++ ++ } ++ catch (TimeoutException e) { ++ incrementConnectionFailures(address); ++ } ++ catch (IOException e) { ++ incrementConnectionFailures(address); ++ } ++ catch (XMPPException e) { ++ incrementConnectionFailures(address); ++ } ++ ++ } ++ ++ // throw exception if connecting to all SOCKS5 proxies failed ++ if (selectedHost == null || socket == null) { ++ cancelRequest(); ++ } ++ ++ // send used-host confirmation ++ Bytestream response = createUsedHostResponse(selectedHost); ++ this.manager.getConnection().sendPacket(response); ++ ++ return new Socks5BytestreamSession(socket, selectedHost.getJID().equals( ++ this.bytestreamRequest.getFrom())); ++ ++ } ++ ++ /** ++ * Rejects the SOCKS5 Bytestream request by sending a reject error to the initiator. ++ */ ++ public void reject() { ++ this.manager.replyRejectPacket(this.bytestreamRequest); ++ } ++ ++ /** ++ * Cancels the SOCKS5 Bytestream request by sending an error to the initiator and building a ++ * XMPP exception. ++ * ++ * @throws XMPPException XMPP exception containing the XMPP error ++ */ ++ private void cancelRequest() throws XMPPException { ++ String errorMessage = "Could not establish socket with any provided host"; ++ XMPPError error = new XMPPError(XMPPError.Condition.item_not_found, errorMessage); ++ IQ errorIQ = IQ.createErrorResponse(this.bytestreamRequest, error); ++ this.manager.getConnection().sendPacket(errorIQ); ++ throw new XMPPException(errorMessage, error); ++ } ++ ++ /** ++ * Returns the response to the SOCKS5 Bytestream request containing the SOCKS5 proxy used. ++ * ++ * @param selectedHost the used SOCKS5 proxy ++ * @return the response to the SOCKS5 Bytestream request ++ */ ++ private Bytestream createUsedHostResponse(StreamHost selectedHost) { ++ Bytestream response = new Bytestream(this.bytestreamRequest.getSessionID()); ++ response.setTo(this.bytestreamRequest.getFrom()); ++ response.setType(IQ.Type.RESULT); ++ response.setPacketID(this.bytestreamRequest.getPacketID()); ++ response.setUsedHost(selectedHost.getJID()); ++ return response; ++ } ++ ++ /** ++ * Increments the connection failure counter by one for the given address. ++ * ++ * @param address the address the connection failure counter should be increased ++ */ ++ private void incrementConnectionFailures(String address) { ++ Integer count = ADDRESS_BLACKLIST.get(address); ++ ADDRESS_BLACKLIST.put(address, count == null ? 1 : count + 1); ++ } ++ ++ /** ++ * Returns how often the connection to the given address failed. ++ * ++ * @param address the address ++ * @return number of connection failures ++ */ ++ private int getConnectionFailures(String address) { ++ Integer count = ADDRESS_BLACKLIST.get(address); ++ return count != null ? count : 0; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamSession.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamSession.java +new file mode 100644 +index 0000000..20fbdb1 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamSession.java +@@ -0,0 +1,81 @@ ++package org.jivesoftware.smackx.bytestreams.socks5; ++ ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.OutputStream; ++import java.net.Socket; ++import java.net.SocketException; ++ ++import org.jivesoftware.smackx.bytestreams.BytestreamSession; ++ ++/** ++ * Socks5BytestreamSession class represents a SOCKS5 Bytestream session. ++ * ++ * @author Henning Staib ++ */ ++public class Socks5BytestreamSession implements BytestreamSession { ++ ++ /* the underlying socket of the SOCKS5 Bytestream */ ++ private final Socket socket; ++ ++ /* flag to indicate if this session is a direct or mediated connection */ ++ private final boolean isDirect; ++ ++ protected Socks5BytestreamSession(Socket socket, boolean isDirect) { ++ this.socket = socket; ++ this.isDirect = isDirect; ++ } ++ ++ /** ++ * Returns <code>true</code> if the session is established through a direct connection between ++ * the initiator and target, <code>false</code> if the session is mediated over a SOCKS proxy. ++ * ++ * @return <code>true</code> if session is a direct connection, <code>false</code> if session is ++ * mediated over a SOCKS5 proxy ++ */ ++ public boolean isDirect() { ++ return this.isDirect; ++ } ++ ++ /** ++ * Returns <code>true</code> if the session is mediated over a SOCKS proxy, <code>false</code> ++ * if this session is established through a direct connection between the initiator and target. ++ * ++ * @return <code>true</code> if session is mediated over a SOCKS5 proxy, <code>false</code> if ++ * session is a direct connection ++ */ ++ public boolean isMediated() { ++ return !this.isDirect; ++ } ++ ++ public InputStream getInputStream() throws IOException { ++ return this.socket.getInputStream(); ++ } ++ ++ public OutputStream getOutputStream() throws IOException { ++ return this.socket.getOutputStream(); ++ } ++ ++ public int getReadTimeout() throws IOException { ++ try { ++ return this.socket.getSoTimeout(); ++ } ++ catch (SocketException e) { ++ throw new IOException("Error on underlying Socket"); ++ } ++ } ++ ++ public void setReadTimeout(int timeout) throws IOException { ++ try { ++ this.socket.setSoTimeout(timeout); ++ } ++ catch (SocketException e) { ++ throw new IOException("Error on underlying Socket"); ++ } ++ } ++ ++ public void close() throws IOException { ++ this.socket.close(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java +new file mode 100644 +index 0000000..664ea59 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java +@@ -0,0 +1,204 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.socks5; ++ ++import java.io.DataInputStream; ++import java.io.DataOutputStream; ++import java.io.IOException; ++import java.net.InetSocketAddress; ++import java.net.Socket; ++import java.net.SocketAddress; ++import java.util.Arrays; ++import java.util.concurrent.Callable; ++import java.util.concurrent.ExecutionException; ++import java.util.concurrent.FutureTask; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.TimeoutException; ++ ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost; ++ ++/** ++ * The SOCKS5 client class handles establishing a connection to a SOCKS5 proxy. Connecting to a ++ * SOCKS5 proxy requires authentication. This implementation only supports the no-authentication ++ * authentication method. ++ * ++ * @author Henning Staib ++ */ ++class Socks5Client { ++ ++ /* stream host containing network settings and name of the SOCKS5 proxy */ ++ protected StreamHost streamHost; ++ ++ /* SHA-1 digest identifying the SOCKS5 stream */ ++ protected String digest; ++ ++ /** ++ * Constructor for a SOCKS5 client. ++ * ++ * @param streamHost containing network settings of the SOCKS5 proxy ++ * @param digest identifying the SOCKS5 Bytestream ++ */ ++ public Socks5Client(StreamHost streamHost, String digest) { ++ this.streamHost = streamHost; ++ this.digest = digest; ++ } ++ ++ /** ++ * Returns the initialized socket that can be used to transfer data between peers via the SOCKS5 ++ * proxy. ++ * ++ * @param timeout timeout to connect to SOCKS5 proxy in milliseconds ++ * @return socket the initialized socket ++ * @throws IOException if initializing the socket failed due to a network error ++ * @throws XMPPException if establishing connection to SOCKS5 proxy failed ++ * @throws TimeoutException if connecting to SOCKS5 proxy timed out ++ * @throws InterruptedException if the current thread was interrupted while waiting ++ */ ++ public Socket getSocket(int timeout) throws IOException, XMPPException, InterruptedException, ++ TimeoutException { ++ ++ // wrap connecting in future for timeout ++ FutureTask<Socket> futureTask = new FutureTask<Socket>(new Callable<Socket>() { ++ ++ public Socket call() throws Exception { ++ ++ // initialize socket ++ Socket socket = new Socket(); ++ SocketAddress socketAddress = new InetSocketAddress(streamHost.getAddress(), ++ streamHost.getPort()); ++ socket.connect(socketAddress); ++ ++ // initialize connection to SOCKS5 proxy ++ if (!establish(socket)) { ++ ++ // initialization failed, close socket ++ socket.close(); ++ throw new XMPPException("establishing connection to SOCKS5 proxy failed"); ++ ++ } ++ ++ return socket; ++ } ++ ++ }); ++ Thread executor = new Thread(futureTask); ++ executor.start(); ++ ++ // get connection to initiator with timeout ++ try { ++ return futureTask.get(timeout, TimeUnit.MILLISECONDS); ++ } ++ catch (ExecutionException e) { ++ Throwable cause = e.getCause(); ++ if (cause != null) { ++ // case exceptions to comply with method signature ++ if (cause instanceof IOException) { ++ throw (IOException) cause; ++ } ++ if (cause instanceof XMPPException) { ++ throw (XMPPException) cause; ++ } ++ } ++ ++ // throw generic IO exception if unexpected exception was thrown ++ throw new IOException("Error while connection to SOCKS5 proxy"); ++ } ++ ++ } ++ ++ /** ++ * Initializes the connection to the SOCKS5 proxy by negotiating authentication method and ++ * requesting a stream for the given digest. Currently only the no-authentication method is ++ * supported by the Socks5Client. ++ * <p> ++ * Returns <code>true</code> if a stream could be established, otherwise <code>false</code>. If ++ * <code>false</code> is returned the given Socket should be closed. ++ * ++ * @param socket connected to a SOCKS5 proxy ++ * @return <code>true</code> if if a stream could be established, otherwise <code>false</code>. ++ * If <code>false</code> is returned the given Socket should be closed. ++ * @throws IOException if a network error occurred ++ */ ++ protected boolean establish(Socket socket) throws IOException { ++ ++ /* ++ * use DataInputStream/DataOutpuStream to assure read and write is completed in a single ++ * statement ++ */ ++ DataInputStream in = new DataInputStream(socket.getInputStream()); ++ DataOutputStream out = new DataOutputStream(socket.getOutputStream()); ++ ++ // authentication negotiation ++ byte[] cmd = new byte[3]; ++ ++ cmd[0] = (byte) 0x05; // protocol version 5 ++ cmd[1] = (byte) 0x01; // number of authentication methods supported ++ cmd[2] = (byte) 0x00; // authentication method: no-authentication required ++ ++ out.write(cmd); ++ out.flush(); ++ ++ byte[] response = new byte[2]; ++ in.readFully(response); ++ ++ // check if server responded with correct version and no-authentication method ++ if (response[0] != (byte) 0x05 || response[1] != (byte) 0x00) { ++ return false; ++ } ++ ++ // request SOCKS5 connection with given address/digest ++ byte[] connectionRequest = createSocks5ConnectRequest(); ++ out.write(connectionRequest); ++ out.flush(); ++ ++ // receive response ++ byte[] connectionResponse; ++ try { ++ connectionResponse = Socks5Utils.receiveSocks5Message(in); ++ } ++ catch (XMPPException e) { ++ return false; // server answered in an unsupported way ++ } ++ ++ // verify response ++ connectionRequest[1] = (byte) 0x00; // set expected return status to 0 ++ return Arrays.equals(connectionRequest, connectionResponse); ++ } ++ ++ /** ++ * Returns a SOCKS5 connection request message. It contains the command "connect", the address ++ * type "domain" and the digest as address. ++ * <p> ++ * (see <a href="http://tools.ietf.org/html/rfc1928">RFC1928</a>) ++ * ++ * @return SOCKS5 connection request message ++ */ ++ private byte[] createSocks5ConnectRequest() { ++ byte addr[] = this.digest.getBytes(); ++ ++ byte[] data = new byte[7 + addr.length]; ++ data[0] = (byte) 0x05; // version (SOCKS5) ++ data[1] = (byte) 0x01; // command (1 - connect) ++ data[2] = (byte) 0x00; // reserved byte (always 0) ++ data[3] = (byte) 0x03; // address type (3 - domain name) ++ data[4] = (byte) addr.length; // address length ++ System.arraycopy(addr, 0, data, 5, addr.length); // address ++ data[data.length - 2] = (byte) 0; // address port (2 bytes always 0) ++ data[data.length - 1] = (byte) 0; ++ ++ return data; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5ClientForInitiator.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5ClientForInitiator.java +new file mode 100644 +index 0000000..0d90791 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5ClientForInitiator.java +@@ -0,0 +1,117 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.socks5; ++ ++import java.io.IOException; ++import java.net.Socket; ++import java.util.concurrent.TimeoutException; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.util.SyncPacketSend; ++import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; ++import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost; ++ ++/** ++ * Implementation of a SOCKS5 client used on the initiators side. This is needed because connecting ++ * to the local SOCKS5 proxy differs form the regular way to connect to a SOCKS5 proxy. Additionally ++ * a remote SOCKS5 proxy has to be activated by the initiator before data can be transferred between ++ * the peers. ++ * ++ * @author Henning Staib ++ */ ++class Socks5ClientForInitiator extends Socks5Client { ++ ++ /* the XMPP connection used to communicate with the SOCKS5 proxy */ ++ private Connection connection; ++ ++ /* the session ID used to activate SOCKS5 stream */ ++ private String sessionID; ++ ++ /* the target JID used to activate SOCKS5 stream */ ++ private String target; ++ ++ /** ++ * Creates a new SOCKS5 client for the initiators side. ++ * ++ * @param streamHost containing network settings of the SOCKS5 proxy ++ * @param digest identifying the SOCKS5 Bytestream ++ * @param connection the XMPP connection ++ * @param sessionID the session ID of the SOCKS5 Bytestream ++ * @param target the target JID of the SOCKS5 Bytestream ++ */ ++ public Socks5ClientForInitiator(StreamHost streamHost, String digest, Connection connection, ++ String sessionID, String target) { ++ super(streamHost, digest); ++ this.connection = connection; ++ this.sessionID = sessionID; ++ this.target = target; ++ } ++ ++ public Socket getSocket(int timeout) throws IOException, XMPPException, InterruptedException, ++ TimeoutException { ++ Socket socket = null; ++ ++ // check if stream host is the local SOCKS5 proxy ++ if (this.streamHost.getJID().equals(this.connection.getUser())) { ++ Socks5Proxy socks5Server = Socks5Proxy.getSocks5Proxy(); ++ socket = socks5Server.getSocket(this.digest); ++ if (socket == null) { ++ throw new XMPPException("target is not connected to SOCKS5 proxy"); ++ } ++ } ++ else { ++ socket = super.getSocket(timeout); ++ ++ try { ++ activate(); ++ } ++ catch (XMPPException e) { ++ socket.close(); ++ throw new XMPPException("activating SOCKS5 Bytestream failed", e); ++ } ++ ++ } ++ ++ return socket; ++ } ++ ++ /** ++ * Activates the SOCKS5 Bytestream by sending a XMPP SOCKS5 Bytestream activation packet to the ++ * SOCKS5 proxy. ++ */ ++ private void activate() throws XMPPException { ++ Bytestream activate = createStreamHostActivation(); ++ // if activation fails #getReply throws an exception ++ SyncPacketSend.getReply(this.connection, activate); ++ } ++ ++ /** ++ * Returns a SOCKS5 Bytestream activation packet. ++ * ++ * @return SOCKS5 Bytestream activation packet ++ */ ++ private Bytestream createStreamHostActivation() { ++ Bytestream activate = new Bytestream(this.sessionID); ++ activate.setMode(null); ++ activate.setType(IQ.Type.SET); ++ activate.setTo(this.streamHost.getJID()); ++ ++ activate.setToActivate(this.target); ++ ++ return activate; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5Proxy.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5Proxy.java +new file mode 100644 +index 0000000..11ef7a9 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5Proxy.java +@@ -0,0 +1,423 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.socks5; ++ ++import java.io.DataInputStream; ++import java.io.DataOutputStream; ++import java.io.IOException; ++import java.net.InetAddress; ++import java.net.ServerSocket; ++import java.net.Socket; ++import java.net.SocketException; ++import java.net.UnknownHostException; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.LinkedHashSet; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++import java.util.concurrent.ConcurrentHashMap; ++ ++import org.jivesoftware.smack.SmackConfiguration; ++import org.jivesoftware.smack.XMPPException; ++ ++/** ++ * The Socks5Proxy class represents a local SOCKS5 proxy server. It can be enabled/disabled by ++ * setting the <code>localSocks5ProxyEnabled</code> flag in the <code>smack-config.xml</code> or by ++ * invoking {@link SmackConfiguration#setLocalSocks5ProxyEnabled(boolean)}. The proxy is enabled by ++ * default. ++ * <p> ++ * The port of the local SOCKS5 proxy can be configured by setting <code>localSocks5ProxyPort</code> ++ * in the <code>smack-config.xml</code> or by invoking ++ * {@link SmackConfiguration#setLocalSocks5ProxyPort(int)}. Default port is 7777. If you set the ++ * port to a negative value Smack tries to the absolute value and all following until it finds an ++ * open port. ++ * <p> ++ * If your application is running on a machine with multiple network interfaces or if you want to ++ * provide your public address in case you are behind a NAT router, invoke ++ * {@link #addLocalAddress(String)} or {@link #replaceLocalAddresses(List)} to modify the list of ++ * local network addresses used for outgoing SOCKS5 Bytestream requests. ++ * <p> ++ * The local SOCKS5 proxy server refuses all connections except the ones that are explicitly allowed ++ * in the process of establishing a SOCKS5 Bytestream ( ++ * {@link Socks5BytestreamManager#establishSession(String)}). ++ * <p> ++ * This Implementation has the following limitations: ++ * <ul> ++ * <li>only supports the no-authentication authentication method</li> ++ * <li>only supports the <code>connect</code> command and will not answer correctly to other ++ * commands</li> ++ * <li>only supports requests with the domain address type and will not correctly answer to requests ++ * with other address types</li> ++ * </ul> ++ * (see <a href="http://tools.ietf.org/html/rfc1928">RFC 1928</a>) ++ * ++ * @author Henning Staib ++ */ ++public class Socks5Proxy { ++ ++ /* SOCKS5 proxy singleton */ ++ private static Socks5Proxy socks5Server; ++ ++ /* reusable implementation of a SOCKS5 proxy server process */ ++ private Socks5ServerProcess serverProcess; ++ ++ /* thread running the SOCKS5 server process */ ++ private Thread serverThread; ++ ++ /* server socket to accept SOCKS5 connections */ ++ private ServerSocket serverSocket; ++ ++ /* assigns a connection to a digest */ ++ private final Map<String, Socket> connectionMap = new ConcurrentHashMap<String, Socket>(); ++ ++ /* list of digests connections should be stored */ ++ private final List<String> allowedConnections = Collections.synchronizedList(new LinkedList<String>()); ++ ++ private final Set<String> localAddresses = Collections.synchronizedSet(new LinkedHashSet<String>()); ++ ++ /** ++ * Private constructor. ++ */ ++ private Socks5Proxy() { ++ this.serverProcess = new Socks5ServerProcess(); ++ ++ // add default local address ++ try { ++ this.localAddresses.add(InetAddress.getLocalHost().getHostAddress()); ++ } ++ catch (UnknownHostException e) { ++ // do nothing ++ } ++ ++ } ++ ++ /** ++ * Returns the local SOCKS5 proxy server. ++ * ++ * @return the local SOCKS5 proxy server ++ */ ++ public static synchronized Socks5Proxy getSocks5Proxy() { ++ if (socks5Server == null) { ++ socks5Server = new Socks5Proxy(); ++ } ++ if (SmackConfiguration.isLocalSocks5ProxyEnabled()) { ++ socks5Server.start(); ++ } ++ return socks5Server; ++ } ++ ++ /** ++ * Starts the local SOCKS5 proxy server. If it is already running, this method does nothing. ++ */ ++ public synchronized void start() { ++ if (isRunning()) { ++ return; ++ } ++ try { ++ if (SmackConfiguration.getLocalSocks5ProxyPort() < 0) { ++ int port = Math.abs(SmackConfiguration.getLocalSocks5ProxyPort()); ++ for (int i = 0; i < 65535 - port; i++) { ++ try { ++ this.serverSocket = new ServerSocket(port + i); ++ break; ++ } ++ catch (IOException e) { ++ // port is used, try next one ++ } ++ } ++ } ++ else { ++ this.serverSocket = new ServerSocket(SmackConfiguration.getLocalSocks5ProxyPort()); ++ } ++ ++ if (this.serverSocket != null) { ++ this.serverThread = new Thread(this.serverProcess); ++ this.serverThread.start(); ++ } ++ } ++ catch (IOException e) { ++ // couldn't setup server ++ System.err.println("couldn't setup local SOCKS5 proxy on port " ++ + SmackConfiguration.getLocalSocks5ProxyPort() + ": " + e.getMessage()); ++ } ++ } ++ ++ /** ++ * Stops the local SOCKS5 proxy server. If it is not running this method does nothing. ++ */ ++ public synchronized void stop() { ++ if (!isRunning()) { ++ return; ++ } ++ ++ try { ++ this.serverSocket.close(); ++ } ++ catch (IOException e) { ++ // do nothing ++ } ++ ++ if (this.serverThread != null && this.serverThread.isAlive()) { ++ try { ++ this.serverThread.interrupt(); ++ this.serverThread.join(); ++ } ++ catch (InterruptedException e) { ++ // do nothing ++ } ++ } ++ this.serverThread = null; ++ this.serverSocket = null; ++ ++ } ++ ++ /** ++ * Adds the given address to the list of local network addresses. ++ * <p> ++ * Use this method if you want to provide multiple addresses in a SOCKS5 Bytestream request. ++ * This may be necessary if your application is running on a machine with multiple network ++ * interfaces or if you want to provide your public address in case you are behind a NAT router. ++ * <p> ++ * The order of the addresses used is determined by the order you add addresses. ++ * <p> ++ * Note that the list of addresses initially contains the address returned by ++ * <code>InetAddress.getLocalHost().getHostAddress()</code>. You can replace the list of ++ * addresses by invoking {@link #replaceLocalAddresses(List)}. ++ * ++ * @param address the local network address to add ++ */ ++ public void addLocalAddress(String address) { ++ if (address == null) { ++ throw new IllegalArgumentException("address may not be null"); ++ } ++ this.localAddresses.add(address); ++ } ++ ++ /** ++ * Removes the given address from the list of local network addresses. This address will then no ++ * longer be used of outgoing SOCKS5 Bytestream requests. ++ * ++ * @param address the local network address to remove ++ */ ++ public void removeLocalAddress(String address) { ++ this.localAddresses.remove(address); ++ } ++ ++ /** ++ * Returns an unmodifiable list of the local network addresses that will be used for streamhost ++ * candidates of outgoing SOCKS5 Bytestream requests. ++ * ++ * @return unmodifiable list of the local network addresses ++ */ ++ public List<String> getLocalAddresses() { ++ return Collections.unmodifiableList(new ArrayList<String>(this.localAddresses)); ++ } ++ ++ /** ++ * Replaces the list of local network addresses. ++ * <p> ++ * Use this method if you want to provide multiple addresses in a SOCKS5 Bytestream request and ++ * want to define their order. This may be necessary if your application is running on a machine ++ * with multiple network interfaces or if you want to provide your public address in case you ++ * are behind a NAT router. ++ * ++ * @param addresses the new list of local network addresses ++ */ ++ public void replaceLocalAddresses(List<String> addresses) { ++ if (addresses == null) { ++ throw new IllegalArgumentException("list must not be null"); ++ } ++ this.localAddresses.clear(); ++ this.localAddresses.addAll(addresses); ++ ++ } ++ ++ /** ++ * Returns the port of the local SOCKS5 proxy server. If it is not running -1 will be returned. ++ * ++ * @return the port of the local SOCKS5 proxy server or -1 if proxy is not running ++ */ ++ public int getPort() { ++ if (!isRunning()) { ++ return -1; ++ } ++ return this.serverSocket.getLocalPort(); ++ } ++ ++ /** ++ * Returns the socket for the given digest. A socket will be returned if the given digest has ++ * been in the list of allowed transfers (see {@link #addTransfer(String)}) while the peer ++ * connected to the SOCKS5 proxy. ++ * ++ * @param digest identifying the connection ++ * @return socket or null if there is no socket for the given digest ++ */ ++ protected Socket getSocket(String digest) { ++ return this.connectionMap.get(digest); ++ } ++ ++ /** ++ * Add the given digest to the list of allowed transfers. Only connections for allowed transfers ++ * are stored and can be retrieved by invoking {@link #getSocket(String)}. All connections to ++ * the local SOCKS5 proxy that don't contain an allowed digest are discarded. ++ * ++ * @param digest to be added to the list of allowed transfers ++ */ ++ protected void addTransfer(String digest) { ++ this.allowedConnections.add(digest); ++ } ++ ++ /** ++ * Removes the given digest from the list of allowed transfers. After invoking this method ++ * already stored connections with the given digest will be removed. ++ * <p> ++ * The digest should be removed after establishing the SOCKS5 Bytestream is finished, an error ++ * occurred while establishing the connection or if the connection is not allowed anymore. ++ * ++ * @param digest to be removed from the list of allowed transfers ++ */ ++ protected void removeTransfer(String digest) { ++ this.allowedConnections.remove(digest); ++ this.connectionMap.remove(digest); ++ } ++ ++ /** ++ * Returns <code>true</code> if the local SOCKS5 proxy server is running, otherwise ++ * <code>false</code>. ++ * ++ * @return <code>true</code> if the local SOCKS5 proxy server is running, otherwise ++ * <code>false</code> ++ */ ++ public boolean isRunning() { ++ return this.serverSocket != null; ++ } ++ ++ /** ++ * Implementation of a simplified SOCKS5 proxy server. ++ */ ++ private class Socks5ServerProcess implements Runnable { ++ ++ public void run() { ++ while (true) { ++ Socket socket = null; ++ ++ try { ++ ++ if (Socks5Proxy.this.serverSocket.isClosed() ++ || Thread.currentThread().isInterrupted()) { ++ return; ++ } ++ ++ // accept connection ++ socket = Socks5Proxy.this.serverSocket.accept(); ++ ++ // initialize connection ++ establishConnection(socket); ++ ++ } ++ catch (SocketException e) { ++ /* ++ * do nothing, if caused by closing the server socket, thread will terminate in ++ * next loop ++ */ ++ } ++ catch (Exception e) { ++ try { ++ if (socket != null) { ++ socket.close(); ++ } ++ } ++ catch (IOException e1) { ++ /* do nothing */ ++ } ++ } ++ } ++ ++ } ++ ++ /** ++ * Negotiates a SOCKS5 connection and stores it on success. ++ * ++ * @param socket connection to the client ++ * @throws XMPPException if client requests a connection in an unsupported way ++ * @throws IOException if a network error occurred ++ */ ++ private void establishConnection(Socket socket) throws XMPPException, IOException { ++ DataOutputStream out = new DataOutputStream(socket.getOutputStream()); ++ DataInputStream in = new DataInputStream(socket.getInputStream()); ++ ++ // first byte is version should be 5 ++ int b = in.read(); ++ if (b != 5) { ++ throw new XMPPException("Only SOCKS5 supported"); ++ } ++ ++ // second byte number of authentication methods supported ++ b = in.read(); ++ ++ // read list of supported authentication methods ++ byte[] auth = new byte[b]; ++ in.readFully(auth); ++ ++ byte[] authMethodSelectionResponse = new byte[2]; ++ authMethodSelectionResponse[0] = (byte) 0x05; // protocol version ++ ++ // only authentication method 0, no authentication, supported ++ boolean noAuthMethodFound = false; ++ for (int i = 0; i < auth.length; i++) { ++ if (auth[i] == (byte) 0x00) { ++ noAuthMethodFound = true; ++ break; ++ } ++ } ++ ++ if (!noAuthMethodFound) { ++ authMethodSelectionResponse[1] = (byte) 0xFF; // no acceptable methods ++ out.write(authMethodSelectionResponse); ++ out.flush(); ++ throw new XMPPException("Authentication method not supported"); ++ } ++ ++ authMethodSelectionResponse[1] = (byte) 0x00; // no-authentication method ++ out.write(authMethodSelectionResponse); ++ out.flush(); ++ ++ // receive connection request ++ byte[] connectionRequest = Socks5Utils.receiveSocks5Message(in); ++ ++ // extract digest ++ String responseDigest = new String(connectionRequest, 5, connectionRequest[4]); ++ ++ // return error if digest is not allowed ++ if (!Socks5Proxy.this.allowedConnections.contains(responseDigest)) { ++ connectionRequest[1] = (byte) 0x05; // set return status to 5 (connection refused) ++ out.write(connectionRequest); ++ out.flush(); ++ ++ throw new XMPPException("Connection is not allowed"); ++ } ++ ++ connectionRequest[1] = (byte) 0x00; // set return status to 0 (success) ++ out.write(connectionRequest); ++ out.flush(); ++ ++ // store connection ++ Socks5Proxy.this.connectionMap.put(responseDigest, socket); ++ } ++ ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5Utils.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5Utils.java +new file mode 100644 +index 0000000..9c92563 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5Utils.java +@@ -0,0 +1,73 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.socks5; ++ ++import java.io.DataInputStream; ++import java.io.IOException; ++ ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.util.StringUtils; ++ ++/** ++ * A collection of utility methods for SOcKS5 messages. ++ * ++ * @author Henning Staib ++ */ ++class Socks5Utils { ++ ++ /** ++ * Returns a SHA-1 digest of the given parameters as specified in <a ++ * href="http://xmpp.org/extensions/xep-0065.html#impl-socks5">XEP-0065</a>. ++ * ++ * @param sessionID for the SOCKS5 Bytestream ++ * @param initiatorJID JID of the initiator of a SOCKS5 Bytestream ++ * @param targetJID JID of the target of a SOCKS5 Bytestream ++ * @return SHA-1 digest of the given parameters ++ */ ++ public static String createDigest(String sessionID, String initiatorJID, String targetJID) { ++ StringBuilder b = new StringBuilder(); ++ b.append(sessionID).append(initiatorJID).append(targetJID); ++ return StringUtils.hash(b.toString()); ++ } ++ ++ /** ++ * Reads a SOCKS5 message from the given InputStream. The message can either be a SOCKS5 request ++ * message or a SOCKS5 response message. ++ * <p> ++ * (see <a href="http://tools.ietf.org/html/rfc1928">RFC1928</a>) ++ * ++ * @param in the DataInputStream to read the message from ++ * @return the SOCKS5 message ++ * @throws IOException if a network error occurred ++ * @throws XMPPException if the SOCKS5 message contains an unsupported address type ++ */ ++ public static byte[] receiveSocks5Message(DataInputStream in) throws IOException, XMPPException { ++ byte[] header = new byte[5]; ++ in.readFully(header, 0, 5); ++ ++ if (header[3] != (byte) 0x03) { ++ throw new XMPPException("Unsupported SOCKS5 address type"); ++ } ++ ++ int addressLength = header[4]; ++ ++ byte[] response = new byte[7 + addressLength]; ++ System.arraycopy(header, 0, response, 0, header.length); ++ ++ in.readFully(response, header.length, addressLength + 2); ++ ++ return response; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/packet/Bytestream.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/packet/Bytestream.java +new file mode 100644 +index 0000000..9e07fc3 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/packet/Bytestream.java +@@ -0,0 +1,474 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.socks5.packet; ++ ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.List; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++/** ++ * A packet representing part of a SOCKS5 Bytestream negotiation. ++ * ++ * @author Alexander Wenckus ++ */ ++public class Bytestream extends IQ { ++ ++ private String sessionID; ++ ++ private Mode mode = Mode.tcp; ++ ++ private final List<StreamHost> streamHosts = new ArrayList<StreamHost>(); ++ ++ private StreamHostUsed usedHost; ++ ++ private Activate toActivate; ++ ++ /** ++ * The default constructor ++ */ ++ public Bytestream() { ++ super(); ++ } ++ ++ /** ++ * A constructor where the session ID can be specified. ++ * ++ * @param SID The session ID related to the negotiation. ++ * @see #setSessionID(String) ++ */ ++ public Bytestream(final String SID) { ++ super(); ++ setSessionID(SID); ++ } ++ ++ /** ++ * Set the session ID related to the bytestream. The session ID is a unique identifier used to ++ * differentiate between stream negotiations. ++ * ++ * @param sessionID the unique session ID that identifies the transfer. ++ */ ++ public void setSessionID(final String sessionID) { ++ this.sessionID = sessionID; ++ } ++ ++ /** ++ * Returns the session ID related to the bytestream negotiation. ++ * ++ * @return Returns the session ID related to the bytestream negotiation. ++ * @see #setSessionID(String) ++ */ ++ public String getSessionID() { ++ return sessionID; ++ } ++ ++ /** ++ * Set the transport mode. This should be put in the initiation of the interaction. ++ * ++ * @param mode the transport mode, either UDP or TCP ++ * @see Mode ++ */ ++ public void setMode(final Mode mode) { ++ this.mode = mode; ++ } ++ ++ /** ++ * Returns the transport mode. ++ * ++ * @return Returns the transport mode. ++ * @see #setMode(Mode) ++ */ ++ public Mode getMode() { ++ return mode; ++ } ++ ++ /** ++ * Adds a potential stream host that the remote user can connect to to receive the file. ++ * ++ * @param JID The JID of the stream host. ++ * @param address The internet address of the stream host. ++ * @return The added stream host. ++ */ ++ public StreamHost addStreamHost(final String JID, final String address) { ++ return addStreamHost(JID, address, 0); ++ } ++ ++ /** ++ * Adds a potential stream host that the remote user can connect to to receive the file. ++ * ++ * @param JID The JID of the stream host. ++ * @param address The internet address of the stream host. ++ * @param port The port on which the remote host is seeking connections. ++ * @return The added stream host. ++ */ ++ public StreamHost addStreamHost(final String JID, final String address, final int port) { ++ StreamHost host = new StreamHost(JID, address); ++ host.setPort(port); ++ addStreamHost(host); ++ ++ return host; ++ } ++ ++ /** ++ * Adds a potential stream host that the remote user can transfer the file through. ++ * ++ * @param host The potential stream host. ++ */ ++ public void addStreamHost(final StreamHost host) { ++ streamHosts.add(host); ++ } ++ ++ /** ++ * Returns the list of stream hosts contained in the packet. ++ * ++ * @return Returns the list of stream hosts contained in the packet. ++ */ ++ public Collection<StreamHost> getStreamHosts() { ++ return Collections.unmodifiableCollection(streamHosts); ++ } ++ ++ /** ++ * Returns the stream host related to the given JID, or null if there is none. ++ * ++ * @param JID The JID of the desired stream host. ++ * @return Returns the stream host related to the given JID, or null if there is none. ++ */ ++ public StreamHost getStreamHost(final String JID) { ++ if (JID == null) { ++ return null; ++ } ++ for (StreamHost host : streamHosts) { ++ if (host.getJID().equals(JID)) { ++ return host; ++ } ++ } ++ ++ return null; ++ } ++ ++ /** ++ * Returns the count of stream hosts contained in this packet. ++ * ++ * @return Returns the count of stream hosts contained in this packet. ++ */ ++ public int countStreamHosts() { ++ return streamHosts.size(); ++ } ++ ++ /** ++ * Upon connecting to the stream host the target of the stream replies to the initiator with the ++ * JID of the SOCKS5 host that they used. ++ * ++ * @param JID The JID of the used host. ++ */ ++ public void setUsedHost(final String JID) { ++ this.usedHost = new StreamHostUsed(JID); ++ } ++ ++ /** ++ * Returns the SOCKS5 host connected to by the remote user. ++ * ++ * @return Returns the SOCKS5 host connected to by the remote user. ++ */ ++ public StreamHostUsed getUsedHost() { ++ return usedHost; ++ } ++ ++ /** ++ * Returns the activate element of the packet sent to the proxy host to verify the identity of ++ * the initiator and match them to the appropriate stream. ++ * ++ * @return Returns the activate element of the packet sent to the proxy host to verify the ++ * identity of the initiator and match them to the appropriate stream. ++ */ ++ public Activate getToActivate() { ++ return toActivate; ++ } ++ ++ /** ++ * Upon the response from the target of the used host the activate packet is sent to the SOCKS5 ++ * proxy. The proxy will activate the stream or return an error after verifying the identity of ++ * the initiator, using the activate packet. ++ * ++ * @param targetID The JID of the target of the file transfer. ++ */ ++ public void setToActivate(final String targetID) { ++ this.toActivate = new Activate(targetID); ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<query xmlns=\"http://jabber.org/protocol/bytestreams\""); ++ if (this.getType().equals(IQ.Type.SET)) { ++ if (getSessionID() != null) { ++ buf.append(" sid=\"").append(getSessionID()).append("\""); ++ } ++ if (getMode() != null) { ++ buf.append(" mode = \"").append(getMode()).append("\""); ++ } ++ buf.append(">"); ++ if (getToActivate() == null) { ++ for (StreamHost streamHost : getStreamHosts()) { ++ buf.append(streamHost.toXML()); ++ } ++ } ++ else { ++ buf.append(getToActivate().toXML()); ++ } ++ } ++ else if (this.getType().equals(IQ.Type.RESULT)) { ++ buf.append(">"); ++ if (getUsedHost() != null) { ++ buf.append(getUsedHost().toXML()); ++ } ++ // A result from the server can also contain stream hosts ++ else if (countStreamHosts() > 0) { ++ for (StreamHost host : streamHosts) { ++ buf.append(host.toXML()); ++ } ++ } ++ } ++ else if (this.getType().equals(IQ.Type.GET)) { ++ return buf.append("/>").toString(); ++ } ++ else { ++ return null; ++ } ++ buf.append("</query>"); ++ ++ return buf.toString(); ++ } ++ ++ /** ++ * Packet extension that represents a potential SOCKS5 proxy for the file transfer. Stream hosts ++ * are forwarded to the target of the file transfer who then chooses and connects to one. ++ * ++ * @author Alexander Wenckus ++ */ ++ public static class StreamHost implements PacketExtension { ++ ++ public static String NAMESPACE = ""; ++ ++ public static String ELEMENTNAME = "streamhost"; ++ ++ private final String JID; ++ ++ private final String addy; ++ ++ private int port = 0; ++ ++ /** ++ * Default constructor. ++ * ++ * @param JID The JID of the stream host. ++ * @param address The internet address of the stream host. ++ */ ++ public StreamHost(final String JID, final String address) { ++ this.JID = JID; ++ this.addy = address; ++ } ++ ++ /** ++ * Returns the JID of the stream host. ++ * ++ * @return Returns the JID of the stream host. ++ */ ++ public String getJID() { ++ return JID; ++ } ++ ++ /** ++ * Returns the internet address of the stream host. ++ * ++ * @return Returns the internet address of the stream host. ++ */ ++ public String getAddress() { ++ return addy; ++ } ++ ++ /** ++ * Sets the port of the stream host. ++ * ++ * @param port The port on which the potential stream host would accept the connection. ++ */ ++ public void setPort(final int port) { ++ this.port = port; ++ } ++ ++ /** ++ * Returns the port on which the potential stream host would accept the connection. ++ * ++ * @return Returns the port on which the potential stream host would accept the connection. ++ */ ++ public int getPort() { ++ return port; ++ } ++ ++ public String getNamespace() { ++ return NAMESPACE; ++ } ++ ++ public String getElementName() { ++ return ELEMENTNAME; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(getElementName()).append(" "); ++ buf.append("jid=\"").append(getJID()).append("\" "); ++ buf.append("host=\"").append(getAddress()).append("\" "); ++ if (getPort() != 0) { ++ buf.append("port=\"").append(getPort()).append("\""); ++ } ++ else { ++ buf.append("zeroconf=\"_jabber.bytestreams\""); ++ } ++ buf.append("/>"); ++ ++ return buf.toString(); ++ } ++ } ++ ++ /** ++ * After selected a SOCKS5 stream host and successfully connecting, the target of the file ++ * transfer returns a byte stream packet with the stream host used extension. ++ * ++ * @author Alexander Wenckus ++ */ ++ public static class StreamHostUsed implements PacketExtension { ++ ++ public String NAMESPACE = ""; ++ ++ public static String ELEMENTNAME = "streamhost-used"; ++ ++ private final String JID; ++ ++ /** ++ * Default constructor. ++ * ++ * @param JID The JID of the selected stream host. ++ */ ++ public StreamHostUsed(final String JID) { ++ this.JID = JID; ++ } ++ ++ /** ++ * Returns the JID of the selected stream host. ++ * ++ * @return Returns the JID of the selected stream host. ++ */ ++ public String getJID() { ++ return JID; ++ } ++ ++ public String getNamespace() { ++ return NAMESPACE; ++ } ++ ++ public String getElementName() { ++ return ELEMENTNAME; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()).append(" "); ++ buf.append("jid=\"").append(getJID()).append("\" "); ++ buf.append("/>"); ++ return buf.toString(); ++ } ++ } ++ ++ /** ++ * The packet sent by the stream initiator to the stream proxy to activate the connection. ++ * ++ * @author Alexander Wenckus ++ */ ++ public static class Activate implements PacketExtension { ++ ++ public String NAMESPACE = ""; ++ ++ public static String ELEMENTNAME = "activate"; ++ ++ private final String target; ++ ++ /** ++ * Default constructor specifying the target of the stream. ++ * ++ * @param target The target of the stream. ++ */ ++ public Activate(final String target) { ++ this.target = target; ++ } ++ ++ /** ++ * Returns the target of the activation. ++ * ++ * @return Returns the target of the activation. ++ */ ++ public String getTarget() { ++ return target; ++ } ++ ++ public String getNamespace() { ++ return NAMESPACE; ++ } ++ ++ public String getElementName() { ++ return ELEMENTNAME; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()).append(">"); ++ buf.append(getTarget()); ++ buf.append("</").append(getElementName()).append(">"); ++ return buf.toString(); ++ } ++ } ++ ++ /** ++ * The stream can be either a TCP stream or a UDP stream. ++ * ++ * @author Alexander Wenckus ++ */ ++ public enum Mode { ++ ++ /** ++ * A TCP based stream. ++ */ ++ tcp, ++ ++ /** ++ * A UDP based stream. ++ */ ++ udp; ++ ++ public static Mode fromName(String name) { ++ Mode mode; ++ try { ++ mode = Mode.valueOf(name); ++ } ++ catch (Exception ex) { ++ mode = tcp; ++ } ++ ++ return mode; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/provider/BytestreamsProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/provider/BytestreamsProvider.java +new file mode 100644 +index 0000000..76f9b0c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/provider/BytestreamsProvider.java +@@ -0,0 +1,82 @@ ++/** ++ * All rights reserved. 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.smackx.bytestreams.socks5.provider; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * Parses a bytestream packet. ++ * ++ * @author Alexander Wenckus ++ */ ++public class BytestreamsProvider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ ++ Bytestream toReturn = new Bytestream(); ++ ++ String id = parser.getAttributeValue("", "sid"); ++ String mode = parser.getAttributeValue("", "mode"); ++ ++ // streamhost ++ String JID = null; ++ String host = null; ++ String port = null; ++ ++ int eventType; ++ String elementName; ++ while (!done) { ++ eventType = parser.next(); ++ elementName = parser.getName(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (elementName.equals(Bytestream.StreamHost.ELEMENTNAME)) { ++ JID = parser.getAttributeValue("", "jid"); ++ host = parser.getAttributeValue("", "host"); ++ port = parser.getAttributeValue("", "port"); ++ } ++ else if (elementName.equals(Bytestream.StreamHostUsed.ELEMENTNAME)) { ++ toReturn.setUsedHost(parser.getAttributeValue("", "jid")); ++ } ++ else if (elementName.equals(Bytestream.Activate.ELEMENTNAME)) { ++ toReturn.setToActivate(parser.getAttributeValue("", "jid")); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (elementName.equals("streamhost")) { ++ if (port == null) { ++ toReturn.addStreamHost(JID, host); ++ } ++ else { ++ toReturn.addStreamHost(JID, host, Integer.parseInt(port)); ++ } ++ JID = null; ++ host = null; ++ port = null; ++ } ++ else if (elementName.equals("query")) { ++ done = true; ++ } ++ } ++ } ++ ++ toReturn.setMode((Bytestream.Mode.fromName(mode))); ++ toReturn.setSessionID(id); ++ return toReturn; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/AdHocCommand.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/AdHocCommand.java +new file mode 100755 +index 0000000..c5d0963 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/AdHocCommand.java +@@ -0,0 +1,450 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2005-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.commands; ++ ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.packet.XMPPError; ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.packet.AdHocCommandData; ++ ++import java.util.List; ++ ++/** ++ * An ad-hoc command is responsible for executing the provided service and ++ * storing the result of the execution. Each new request will create a new ++ * instance of the command, allowing information related to executions to be ++ * stored in it. For example suppose that a command that retrieves the list of ++ * users on a server is implemented. When the command is executed it gets that ++ * list and the result is stored as a form in the command instance, i.e. the ++ * <code>getForm</code> method retrieves a form with all the users. ++ * <p> ++ * Each command has a <tt>node</tt> that should be unique within a given JID. ++ * <p> ++ * Commands may have zero or more stages. Each stage is usually used for ++ * gathering information required for the command execution. Users are able to ++ * move forward or backward across the different stages. Commands may not be ++ * cancelled while they are being executed. However, users may request the ++ * "cancel" action when submitting a stage response indicating that the command ++ * execution should be aborted. Thus, releasing any collected information. ++ * Commands that require user interaction (i.e. have more than one stage) will ++ * have to provide the data forms the user must complete in each stage and the ++ * allowed actions the user might perform during each stage (e.g. go to the ++ * previous stage or go to the next stage). ++ * <p> ++ * All the actions may throw an XMPPException if there is a problem executing ++ * them. The <code>XMPPError</code> of that exception may have some specific ++ * information about the problem. The possible extensions are: ++ * ++ * <li><i>malformed-action</i>. Extension of a <i>bad-request</i> error.</li> ++ * <li><i>bad-action</i>. Extension of a <i>bad-request</i> error.</li> ++ * <li><i>bad-locale</i>. Extension of a <i>bad-request</i> error.</li> ++ * <li><i>bad-payload</i>. Extension of a <i>bad-request</i> error.</li> ++ * <li><i>bad-sessionid</i>. Extension of a <i>bad-request</i> error.</li> ++ * <li><i>session-expired</i>. Extension of a <i>not-allowed</i> error.</li> ++ * <p> ++ * See the <code>SpecificErrorCondition</code> class for detailed description ++ * of each one. ++ * <p> ++ * Use the <code>getSpecificErrorConditionFrom</code> to obtain the specific ++ * information from an <code>XMPPError</code>. ++ * ++ * @author Gabriel Guardincerri ++ * ++ */ ++public abstract class AdHocCommand { ++ // TODO: Analyze the redesign of command by having an ExecutionResponse as a ++ // TODO: result to the execution of every action. That result should have all the ++ // TODO: information related to the execution, e.g. the form to fill. Maybe this ++ // TODO: design is more intuitive and simpler than the current one that has all in ++ // TODO: one class. ++ ++ private AdHocCommandData data; ++ ++ public AdHocCommand() { ++ super(); ++ data = new AdHocCommandData(); ++ } ++ ++ /** ++ * Returns the specific condition of the <code>error</code> or <tt>null</tt> if the ++ * error doesn't have any. ++ * ++ * @param error the error the get the specific condition from. ++ * @return the specific condition of this error, or null if it doesn't have ++ * any. ++ */ ++ public static SpecificErrorCondition getSpecificErrorCondition(XMPPError error) { ++ // This method is implemented to provide an easy way of getting a packet ++ // extension of the XMPPError. ++ for (SpecificErrorCondition condition : SpecificErrorCondition.values()) { ++ if (error.getExtension(condition.toString(), ++ AdHocCommandData.SpecificError.namespace) != null) { ++ return condition; ++ } ++ } ++ return null; ++ } ++ ++ /** ++ * Set the the human readable name of the command, usually used for ++ * displaying in a UI. ++ * ++ * @param name the name. ++ */ ++ public void setName(String name) { ++ data.setName(name); ++ } ++ ++ /** ++ * Returns the human readable name of the command. ++ * ++ * @return the human readable name of the command ++ */ ++ public String getName() { ++ return data.getName(); ++ } ++ ++ /** ++ * Sets the unique identifier of the command. This value must be unique for ++ * the <code>OwnerJID</code>. ++ * ++ * @param node the unique identifier of the command. ++ */ ++ public void setNode(String node) { ++ data.setNode(node); ++ } ++ ++ /** ++ * Returns the unique identifier of the command. It is unique for the ++ * <code>OwnerJID</code>. ++ * ++ * @return the unique identifier of the command. ++ */ ++ public String getNode() { ++ return data.getNode(); ++ } ++ ++ /** ++ * Returns the full JID of the owner of this command. This JID is the "to" of a ++ * execution request. ++ * ++ * @return the owner JID. ++ */ ++ public abstract String getOwnerJID(); ++ ++ /** ++ * Returns the notes that the command has at the current stage. ++ * ++ * @return a list of notes. ++ */ ++ public List<AdHocCommandNote> getNotes() { ++ return data.getNotes(); ++ } ++ ++ /** ++ * Adds a note to the current stage. This should be used when setting a ++ * response to the execution of an action. All the notes added here are ++ * returned by the {@link #getNotes} method during the current stage. ++ * Once the stage changes all the notes are discarded. ++ * ++ * @param note the note. ++ */ ++ protected void addNote(AdHocCommandNote note) { ++ data.addNote(note); ++ } ++ ++ public String getRaw() { ++ return data.getChildElementXML(); ++ } ++ ++ /** ++ * Returns the form of the current stage. Usually it is the form that must ++ * be answered to execute the next action. If that is the case it should be ++ * used by the requester to fill all the information that the executor needs ++ * to continue to the next stage. It can also be the result of the ++ * execution. ++ * ++ * @return the form of the current stage to fill out or the result of the ++ * execution. ++ */ ++ public Form getForm() { ++ if (data.getForm() == null) { ++ return null; ++ } ++ else { ++ return new Form(data.getForm()); ++ } ++ } ++ ++ /** ++ * Sets the form of the current stage. This should be used when setting a ++ * response. It could be a form to fill out the information needed to go to ++ * the next stage or the result of an execution. ++ * ++ * @param form the form of the current stage to fill out or the result of the ++ * execution. ++ */ ++ protected void setForm(Form form) { ++ data.setForm(form.getDataFormToSend()); ++ } ++ ++ /** ++ * Executes the command. This is invoked only on the first stage of the ++ * command. It is invoked on every command. If there is a problem executing ++ * the command it throws an XMPPException. ++ * ++ * @throws XMPPException if there is an error executing the command. ++ */ ++ public abstract void execute() throws XMPPException; ++ ++ /** ++ * Executes the next action of the command with the information provided in ++ * the <code>response</code>. This form must be the answer form of the ++ * previous stage. This method will be only invoked for commands that have one ++ * or more stages. If there is a problem executing the command it throws an ++ * XMPPException. ++ * ++ * @param response the form answer of the previous stage. ++ * @throws XMPPException if there is a problem executing the command. ++ */ ++ public abstract void next(Form response) throws XMPPException; ++ ++ /** ++ * Completes the command execution with the information provided in the ++ * <code>response</code>. This form must be the answer form of the ++ * previous stage. This method will be only invoked for commands that have one ++ * or more stages. If there is a problem executing the command it throws an ++ * XMPPException. ++ * ++ * @param response the form answer of the previous stage. ++ * @throws XMPPException if there is a problem executing the command. ++ */ ++ public abstract void complete(Form response) throws XMPPException; ++ ++ /** ++ * Goes to the previous stage. The requester is asking to re-send the ++ * information of the previous stage. The command must change it state to ++ * the previous one. If there is a problem executing the command it throws ++ * an XMPPException. ++ * ++ * @throws XMPPException if there is a problem executing the command. ++ */ ++ public abstract void prev() throws XMPPException; ++ ++ /** ++ * Cancels the execution of the command. This can be invoked on any stage of ++ * the execution. If there is a problem executing the command it throws an ++ * XMPPException. ++ * ++ * @throws XMPPException if there is a problem executing the command. ++ */ ++ public abstract void cancel() throws XMPPException; ++ ++ /** ++ * Returns a collection with the allowed actions based on the current stage. ++ * Possible actions are: {@link Action#prev prev}, {@link Action#next next} and ++ * {@link Action#complete complete}. This method will be only invoked for commands that ++ * have one or more stages. ++ * ++ * @return a collection with the allowed actions based on the current stage ++ * as defined in the SessionData. ++ */ ++ protected List<Action> getActions() { ++ return data.getActions(); ++ } ++ ++ /** ++ * Add an action to the current stage available actions. This should be used ++ * when creating a response. ++ * ++ * @param action the action. ++ */ ++ protected void addActionAvailable(Action action) { ++ data.addAction(action); ++ } ++ ++ /** ++ * Returns the action available for the current stage which is ++ * considered the equivalent to "execute". When the requester sends his ++ * reply, if no action was defined in the command then the action will be ++ * assumed "execute" thus assuming the action returned by this method. This ++ * method will never be invoked for commands that have no stages. ++ * ++ * @return the action available for the current stage which is considered ++ * the equivalent to "execute". ++ */ ++ protected Action getExecuteAction() { ++ return data.getExecuteAction(); ++ } ++ ++ /** ++ * Sets which of the actions available for the current stage is ++ * considered the equivalent to "execute". This should be used when setting ++ * a response. When the requester sends his reply, if no action was defined ++ * in the command then the action will be assumed "execute" thus assuming ++ * the action returned by this method. ++ * ++ * @param action the action. ++ */ ++ protected void setExecuteAction(Action action) { ++ data.setExecuteAction(action); ++ } ++ ++ /** ++ * Returns the status of the current stage. ++ * ++ * @return the current status. ++ */ ++ public Status getStatus() { ++ return data.getStatus(); ++ } ++ ++ /** ++ * Sets the data of the current stage. This should not used. ++ * ++ * @param data the data. ++ */ ++ void setData(AdHocCommandData data) { ++ this.data = data; ++ } ++ ++ /** ++ * Gets the data of the current stage. This should not used. ++ * ++ * @return the data. ++ */ ++ AdHocCommandData getData() { ++ return data; ++ } ++ ++ /** ++ * Returns true if the <code>action</code> is available in the current stage. ++ * The {@link Action#cancel cancel} action is always allowed. To define the ++ * available actions use the <code>addActionAvailable</code> method. ++ * ++ * @param action ++ * The action to check if it is available. ++ * @return True if the action is available for the current stage. ++ */ ++ protected boolean isValidAction(Action action) { ++ return getActions().contains(action) || Action.cancel.equals(action); ++ } ++ ++ /** ++ * The status of the stage in the adhoc command. ++ */ ++ public enum Status { ++ ++ /** ++ * The command is being executed. ++ */ ++ executing, ++ ++ /** ++ * The command has completed. The command session has ended. ++ */ ++ completed, ++ ++ /** ++ * The command has been canceled. The command session has ended. ++ */ ++ canceled ++ } ++ ++ public enum Action { ++ ++ /** ++ * The command should be executed or continue to be executed. This is ++ * the default value. ++ */ ++ execute, ++ ++ /** ++ * The command should be canceled. ++ */ ++ cancel, ++ ++ /** ++ * The command should be digress to the previous stage of execution. ++ */ ++ prev, ++ ++ /** ++ * The command should progress to the next stage of execution. ++ */ ++ next, ++ ++ /** ++ * The command should be completed (if possible). ++ */ ++ complete, ++ ++ /** ++ * The action is unknow. This is used when a recieved message has an ++ * unknown action. It must not be used to send an execution request. ++ */ ++ unknown ++ } ++ ++ public enum SpecificErrorCondition { ++ ++ /** ++ * The responding JID cannot accept the specified action. ++ */ ++ badAction("bad-action"), ++ ++ /** ++ * The responding JID does not understand the specified action. ++ */ ++ malformedAction("malformed-action"), ++ ++ /** ++ * The responding JID cannot accept the specified language/locale. ++ */ ++ badLocale("bad-locale"), ++ ++ /** ++ * The responding JID cannot accept the specified payload (e.g. the data ++ * form did not provide one or more required fields). ++ */ ++ badPayload("bad-payload"), ++ ++ /** ++ * The responding JID cannot accept the specified sessionid. ++ */ ++ badSessionid("bad-sessionid"), ++ ++ /** ++ * The requesting JID specified a sessionid that is no longer active ++ * (either because it was completed, canceled, or timed out). ++ */ ++ sessionExpired("session-expired"); ++ ++ private String value; ++ ++ SpecificErrorCondition(String value) { ++ this.value = value; ++ } ++ ++ public String toString() { ++ return value; ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/AdHocCommandManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/AdHocCommandManager.java +new file mode 100755 +index 0000000..7496bf6 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/AdHocCommandManager.java +@@ -0,0 +1,740 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2005-2008 Jive Software. ++ * ++ * All rights reserved. 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.smackx.commands; ++ ++import org.jivesoftware.smack.*; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.XMPPError; ++import org.jivesoftware.smack.util.StringUtils; ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.NodeInformationProvider; ++import org.jivesoftware.smackx.ServiceDiscoveryManager; ++import org.jivesoftware.smackx.commands.AdHocCommand.Action; ++import org.jivesoftware.smackx.commands.AdHocCommand.Status; ++import org.jivesoftware.smackx.packet.AdHocCommandData; ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++import org.jivesoftware.smackx.packet.DiscoverInfo.Identity; ++import org.jivesoftware.smackx.packet.DiscoverItems; ++ ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.List; ++import java.util.Map; ++import java.util.concurrent.ConcurrentHashMap; ++ ++/** ++ * An AdHocCommandManager is responsible for keeping the list of available ++ * commands offered by a service and for processing commands requests. ++ * ++ * Pass in a Connection instance to ++ * {@link #getAddHocCommandsManager(org.jivesoftware.smack.Connection)} in order to ++ * get an instance of this class. ++ * ++ * @author Gabriel Guardincerri ++ */ ++public class AdHocCommandManager { ++ ++ private static final String DISCO_NAMESPACE = "http://jabber.org/protocol/commands"; ++ ++ private static final String discoNode = DISCO_NAMESPACE; ++ ++ /** ++ * The session time out in seconds. ++ */ ++ private static final int SESSION_TIMEOUT = 2 * 60; ++ ++ /** ++ * Map a Connection with it AdHocCommandManager. This map have a key-value ++ * pair for every active connection. ++ */ ++ private static Map<Connection, AdHocCommandManager> instances = ++ new ConcurrentHashMap<Connection, AdHocCommandManager>(); ++ ++ /** ++ * Register the listener for all the connection creations. When a new ++ * connection is created a new AdHocCommandManager is also created and ++ * related to that connection. ++ */ ++ static { ++ Connection.addConnectionCreationListener(new ConnectionCreationListener() { ++ public void connectionCreated(Connection connection) { ++ new AdHocCommandManager(connection); ++ } ++ }); ++ } ++ ++ /** ++ * Returns the <code>AdHocCommandManager</code> related to the ++ * <code>connection</code>. ++ * ++ * @param connection the XMPP connection. ++ * @return the AdHocCommandManager associated with the connection. ++ */ ++ public static AdHocCommandManager getAddHocCommandsManager(Connection connection) { ++ return instances.get(connection); ++ } ++ ++ /** ++ * Thread that reaps stale sessions. ++ */ ++ private Thread sessionsSweeper; ++ ++ /** ++ * The Connection that this instances of AdHocCommandManager manages ++ */ ++ private Connection connection; ++ ++ /** ++ * Map a command node with its AdHocCommandInfo. Note: Key=command node, ++ * Value=command. Command node matches the node attribute sent by command ++ * requesters. ++ */ ++ private Map<String, AdHocCommandInfo> commands = new ConcurrentHashMap<String, AdHocCommandInfo>(); ++ ++ /** ++ * Map a command session ID with the instance LocalCommand. The LocalCommand ++ * is the an objects that has all the information of the current state of ++ * the command execution. Note: Key=session ID, Value=LocalCommand. Session ++ * ID matches the sessionid attribute sent by command responders. ++ */ ++ private Map<String, LocalCommand> executingCommands = new ConcurrentHashMap<String, LocalCommand>(); ++ ++ private AdHocCommandManager(Connection connection) { ++ super(); ++ this.connection = connection; ++ init(); ++ } ++ ++ /** ++ * Registers a new command with this command manager, which is related to a ++ * connection. The <tt>node</tt> is an unique identifier of that command for ++ * the connection related to this command manager. The <tt>name</tt> is the ++ * human readable name of the command. The <tt>class</tt> is the class of ++ * the command, which must extend {@link LocalCommand} and have a default ++ * constructor. ++ * ++ * @param node the unique identifier of the command. ++ * @param name the human readable name of the command. ++ * @param clazz the class of the command, which must extend {@link LocalCommand}. ++ */ ++ public void registerCommand(String node, String name, final Class clazz) { ++ registerCommand(node, name, new LocalCommandFactory() { ++ public LocalCommand getInstance() throws InstantiationException, IllegalAccessException { ++ return (LocalCommand)clazz.newInstance(); ++ } ++ }); ++ } ++ ++ /** ++ * Registers a new command with this command manager, which is related to a ++ * connection. The <tt>node</tt> is an unique identifier of that ++ * command for the connection related to this command manager. The <tt>name</tt> ++ * is the human readeale name of the command. The <tt>factory</tt> generates ++ * new instances of the command. ++ * ++ * @param node the unique identifier of the command. ++ * @param name the human readable name of the command. ++ * @param factory a factory to create new instances of the command. ++ */ ++ public void registerCommand(String node, final String name, LocalCommandFactory factory) { ++ AdHocCommandInfo commandInfo = new AdHocCommandInfo(node, name, connection.getUser(), factory); ++ ++ commands.put(node, commandInfo); ++ // Set the NodeInformationProvider that will provide information about ++ // the added command ++ ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider(node, ++ new NodeInformationProvider() { ++ public List<DiscoverItems.Item> getNodeItems() { ++ return null; ++ } ++ ++ public List<String> getNodeFeatures() { ++ List<String> answer = new ArrayList<String>(); ++ answer.add(DISCO_NAMESPACE); ++ // TODO: check if this service is provided by the ++ // TODO: current connection. ++ answer.add("jabber:x:data"); ++ return answer; ++ } ++ ++ public List<DiscoverInfo.Identity> getNodeIdentities() { ++ List<DiscoverInfo.Identity> answer = new ArrayList<DiscoverInfo.Identity>(); ++ DiscoverInfo.Identity identity = new DiscoverInfo.Identity( ++ "automation", name); ++ identity.setType("command-node"); ++ answer.add(identity); ++ return answer; ++ } ++ ++ }); ++ } ++ ++ /** ++ * Discover the commands of an specific JID. The <code>jid</code> is a ++ * full JID. ++ * ++ * @param jid the full JID to retrieve the commands for. ++ * @return the discovered items. ++ * @throws XMPPException if the operation failed for some reason. ++ */ ++ public DiscoverItems discoverCommands(String jid) throws XMPPException { ++ ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager ++ .getInstanceFor(connection); ++ return serviceDiscoveryManager.discoverItems(jid, discoNode); ++ } ++ ++ /** ++ * Publish the commands to an specific JID. ++ * ++ * @param jid the full JID to publish the commands to. ++ * @throws XMPPException if the operation failed for some reason. ++ */ ++ public void publishCommands(String jid) throws XMPPException { ++ ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager ++ .getInstanceFor(connection); ++ ++ // Collects the commands to publish as items ++ DiscoverItems discoverItems = new DiscoverItems(); ++ Collection<AdHocCommandInfo> xCommandsList = getRegisteredCommands(); ++ ++ for (AdHocCommandInfo info : xCommandsList) { ++ DiscoverItems.Item item = new DiscoverItems.Item(info.getOwnerJID()); ++ item.setName(info.getName()); ++ item.setNode(info.getNode()); ++ discoverItems.addItem(item); ++ } ++ ++ serviceDiscoveryManager.publishItems(jid, discoNode, discoverItems); ++ } ++ ++ /** ++ * Returns a command that represents an instance of a command in a remote ++ * host. It is used to execute remote commands. The concept is similar to ++ * RMI. Every invocation on this command is equivalent to an invocation in ++ * the remote command. ++ * ++ * @param jid the full JID of the host of the remote command ++ * @param node the identifier of the command ++ * @return a local instance equivalent to the remote command. ++ */ ++ public RemoteCommand getRemoteCommand(String jid, String node) { ++ return new RemoteCommand(connection, node, jid); ++ } ++ ++ /** ++ * <ul> ++ * <li>Adds listeners to the connection</li> ++ * <li>Registers the ad-hoc command feature to the ServiceDiscoveryManager</li> ++ * <li>Registers the items of the feature</li> ++ * <li>Adds packet listeners to handle execution requests</li> ++ * <li>Creates and start the session sweeper</li> ++ * </ul> ++ */ ++ private void init() { ++ // Register the new instance and associate it with the connection ++ instances.put(connection, this); ++ ++ // Add a listener to the connection that removes the registered instance ++ // when the connection is closed ++ connection.addConnectionListener(new ConnectionListener() { ++ public void connectionClosed() { ++ // Unregister this instance since the connection has been closed ++ instances.remove(connection); ++ } ++ ++ public void connectionClosedOnError(Exception e) { ++ // Unregister this instance since the connection has been closed ++ instances.remove(connection); ++ } ++ ++ public void reconnectionSuccessful() { ++ // Register this instance since the connection has been ++ // reestablished ++ instances.put(connection, AdHocCommandManager.this); ++ } ++ ++ public void reconnectingIn(int seconds) { ++ // Nothing to do ++ } ++ ++ public void reconnectionFailed(Exception e) { ++ // Nothing to do ++ } ++ }); ++ ++ // Add the feature to the service discovery manage to show that this ++ // connection supports the AdHoc-Commands protocol. ++ // This information will be used when another client tries to ++ // discover whether this client supports AdHoc-Commands or not. ++ ServiceDiscoveryManager.getInstanceFor(connection).addFeature( ++ DISCO_NAMESPACE); ++ ++ // Set the NodeInformationProvider that will provide information about ++ // which AdHoc-Commands are registered, whenever a disco request is ++ // received ++ ServiceDiscoveryManager.getInstanceFor(connection) ++ .setNodeInformationProvider(discoNode, ++ new NodeInformationProvider() { ++ public List<DiscoverItems.Item> getNodeItems() { ++ ++ List<DiscoverItems.Item> answer = new ArrayList<DiscoverItems.Item>(); ++ Collection<AdHocCommandInfo> commandsList = getRegisteredCommands(); ++ ++ for (AdHocCommandInfo info : commandsList) { ++ DiscoverItems.Item item = new DiscoverItems.Item( ++ info.getOwnerJID()); ++ item.setName(info.getName()); ++ item.setNode(info.getNode()); ++ answer.add(item); ++ } ++ ++ return answer; ++ } ++ ++ public List<String> getNodeFeatures() { ++ return null; ++ } ++ ++ public List<Identity> getNodeIdentities() { ++ return null; ++ } ++ }); ++ ++ // The packet listener and the filter for processing some AdHoc Commands ++ // Packets ++ PacketListener listener = new PacketListener() { ++ public void processPacket(Packet packet) { ++ AdHocCommandData requestData = (AdHocCommandData) packet; ++ processAdHocCommand(requestData); ++ } ++ }; ++ ++ PacketFilter filter = new PacketTypeFilter(AdHocCommandData.class); ++ connection.addPacketListener(listener, filter); ++ ++ sessionsSweeper = null; ++ } ++ ++ /** ++ * Process the AdHoc-Command packet that request the execution of some ++ * action of a command. If this is the first request, this method checks, ++ * before executing the command, if: ++ * <ul> ++ * <li>The requested command exists</li> ++ * <li>The requester has permissions to execute it</li> ++ * <li>The command has more than one stage, if so, it saves the command and ++ * session ID for further use</li> ++ * </ul> ++ * ++ * <br> ++ * <br> ++ * If this is not the first request, this method checks, before executing ++ * the command, if: ++ * <ul> ++ * <li>The session ID of the request was stored</li> ++ * <li>The session life do not exceed the time out</li> ++ * <li>The action to execute is one of the available actions</li> ++ * </ul> ++ * ++ * @param requestData ++ * the packet to process. ++ */ ++ private void processAdHocCommand(AdHocCommandData requestData) { ++ // Only process requests of type SET ++ if (requestData.getType() != IQ.Type.SET) { ++ return; ++ } ++ ++ // Creates the response with the corresponding data ++ AdHocCommandData response = new AdHocCommandData(); ++ response.setTo(requestData.getFrom()); ++ response.setPacketID(requestData.getPacketID()); ++ response.setNode(requestData.getNode()); ++ response.setId(requestData.getTo()); ++ ++ String sessionId = requestData.getSessionID(); ++ String commandNode = requestData.getNode(); ++ ++ if (sessionId == null) { ++ // A new execution request has been received. Check that the ++ // command exists ++ if (!commands.containsKey(commandNode)) { ++ // Requested command does not exist so return ++ // item_not_found error. ++ respondError(response, XMPPError.Condition.item_not_found); ++ return; ++ } ++ ++ // Create new session ID ++ sessionId = StringUtils.randomString(15); ++ ++ try { ++ // Create a new instance of the command with the ++ // corresponding sessioid ++ LocalCommand command = newInstanceOfCmd(commandNode, sessionId); ++ ++ response.setType(IQ.Type.RESULT); ++ command.setData(response); ++ ++ // Check that the requester has enough permission. ++ // Answer forbidden error if requester permissions are not ++ // enough to execute the requested command ++ if (!command.hasPermission(requestData.getFrom())) { ++ respondError(response, XMPPError.Condition.forbidden); ++ return; ++ } ++ ++ Action action = requestData.getAction(); ++ ++ // If the action is unknown then respond an error. ++ if (action != null && action.equals(Action.unknown)) { ++ respondError(response, XMPPError.Condition.bad_request, ++ AdHocCommand.SpecificErrorCondition.malformedAction); ++ return; ++ } ++ ++ // If the action is not execute, then it is an invalid action. ++ if (action != null && !action.equals(Action.execute)) { ++ respondError(response, XMPPError.Condition.bad_request, ++ AdHocCommand.SpecificErrorCondition.badAction); ++ return; ++ } ++ ++ // Increase the state number, so the command knows in witch ++ // stage it is ++ command.incrementStage(); ++ // Executes the command ++ command.execute(); ++ ++ if (command.isLastStage()) { ++ // If there is only one stage then the command is completed ++ response.setStatus(Status.completed); ++ } ++ else { ++ // Else it is still executing, and is registered to be ++ // available for the next call ++ response.setStatus(Status.executing); ++ executingCommands.put(sessionId, command); ++ // See if the session reaping thread is started. If not, start it. ++ if (sessionsSweeper == null) { ++ sessionsSweeper = new Thread(new Runnable() { ++ public void run() { ++ while (true) { ++ for (String sessionId : executingCommands.keySet()) { ++ LocalCommand command = executingCommands.get(sessionId); ++ // Since the command could be removed in the meanwhile ++ // of getting the key and getting the value - by a ++ // processed packet. We must check if it still in the ++ // map. ++ if (command != null) { ++ long creationStamp = command.getCreationDate(); ++ // Check if the Session data has expired (default is ++ // 10 minutes) ++ // To remove it from the session list it waits for ++ // the double of the of time out time. This is to ++ // let ++ // the requester know why his execution request is ++ // not accepted. If the session is removed just ++ // after the time out, then whe the user request to ++ // continue the execution he will recieved an ++ // invalid session error and not a time out error. ++ if (System.currentTimeMillis() - creationStamp > SESSION_TIMEOUT * 1000 * 2) { ++ // Remove the expired session ++ executingCommands.remove(sessionId); ++ } ++ } ++ } ++ try { ++ Thread.sleep(1000); ++ } ++ catch (InterruptedException ie) { ++ // Ignore. ++ } ++ } ++ } ++ ++ }); ++ sessionsSweeper.setDaemon(true); ++ sessionsSweeper.start(); ++ } ++ } ++ ++ // Sends the response packet ++ connection.sendPacket(response); ++ ++ } ++ catch (XMPPException e) { ++ // If there is an exception caused by the next, complete, ++ // prev or cancel method, then that error is returned to the ++ // requester. ++ XMPPError error = e.getXMPPError(); ++ ++ // If the error type is cancel, then the execution is ++ // canceled therefore the status must show that, and the ++ // command be removed from the executing list. ++ if (XMPPError.Type.CANCEL.equals(error.getType())) { ++ response.setStatus(Status.canceled); ++ executingCommands.remove(sessionId); ++ } ++ respondError(response, error); ++ e.printStackTrace(); ++ } ++ } ++ else { ++ LocalCommand command = executingCommands.get(sessionId); ++ ++ // Check that a command exists for the specified sessionID ++ // This also handles if the command was removed in the meanwhile ++ // of getting the key and the value of the map. ++ if (command == null) { ++ respondError(response, XMPPError.Condition.bad_request, ++ AdHocCommand.SpecificErrorCondition.badSessionid); ++ return; ++ } ++ ++ // Check if the Session data has expired (default is 10 minutes) ++ long creationStamp = command.getCreationDate(); ++ if (System.currentTimeMillis() - creationStamp > SESSION_TIMEOUT * 1000) { ++ // Remove the expired session ++ executingCommands.remove(sessionId); ++ ++ // Answer a not_allowed error (session-expired) ++ respondError(response, XMPPError.Condition.not_allowed, ++ AdHocCommand.SpecificErrorCondition.sessionExpired); ++ return; ++ } ++ ++ /* ++ * Since the requester could send two requests for the same ++ * executing command i.e. the same session id, all the execution of ++ * the action must be synchronized to avoid inconsistencies. ++ */ ++ synchronized (command) { ++ Action action = requestData.getAction(); ++ ++ // If the action is unknown the respond an error ++ if (action != null && action.equals(Action.unknown)) { ++ respondError(response, XMPPError.Condition.bad_request, ++ AdHocCommand.SpecificErrorCondition.malformedAction); ++ return; ++ } ++ ++ // If the user didn't specify an action or specify the execute ++ // action then follow the actual default execute action ++ if (action == null || Action.execute.equals(action)) { ++ action = command.getExecuteAction(); ++ } ++ ++ // Check that the specified action was previously ++ // offered ++ if (!command.isValidAction(action)) { ++ respondError(response, XMPPError.Condition.bad_request, ++ AdHocCommand.SpecificErrorCondition.badAction); ++ return; ++ } ++ ++ try { ++ // TODO: Check that all the requierd fields of the form are ++ // TODO: filled, if not throw an exception. This will simplify the ++ // TODO: construction of new commands ++ ++ // Since all errors were passed, the response is now a ++ // result ++ response.setType(IQ.Type.RESULT); ++ ++ // Set the new data to the command. ++ command.setData(response); ++ ++ if (Action.next.equals(action)) { ++ command.incrementStage(); ++ command.next(new Form(requestData.getForm())); ++ if (command.isLastStage()) { ++ // If it is the last stage then the command is ++ // completed ++ response.setStatus(Status.completed); ++ } ++ else { ++ // Otherwise it is still executing ++ response.setStatus(Status.executing); ++ } ++ } ++ else if (Action.complete.equals(action)) { ++ command.incrementStage(); ++ command.complete(new Form(requestData.getForm())); ++ response.setStatus(Status.completed); ++ // Remove the completed session ++ executingCommands.remove(sessionId); ++ } ++ else if (Action.prev.equals(action)) { ++ command.decrementStage(); ++ command.prev(); ++ } ++ else if (Action.cancel.equals(action)) { ++ command.cancel(); ++ response.setStatus(Status.canceled); ++ // Remove the canceled session ++ executingCommands.remove(sessionId); ++ } ++ ++ connection.sendPacket(response); ++ } ++ catch (XMPPException e) { ++ // If there is an exception caused by the next, complete, ++ // prev or cancel method, then that error is returned to the ++ // requester. ++ XMPPError error = e.getXMPPError(); ++ ++ // If the error type is cancel, then the execution is ++ // canceled therefore the status must show that, and the ++ // command be removed from the executing list. ++ if (XMPPError.Type.CANCEL.equals(error.getType())) { ++ response.setStatus(Status.canceled); ++ executingCommands.remove(sessionId); ++ } ++ respondError(response, error); ++ ++ e.printStackTrace(); ++ } ++ } ++ } ++ } ++ ++ /** ++ * Responds an error with an specific condition. ++ * ++ * @param response the response to send. ++ * @param condition the condition of the error. ++ */ ++ private void respondError(AdHocCommandData response, ++ XMPPError.Condition condition) { ++ respondError(response, new XMPPError(condition)); ++ } ++ ++ /** ++ * Responds an error with an specific condition. ++ * ++ * @param response the response to send. ++ * @param condition the condition of the error. ++ * @param specificCondition the adhoc command error condition. ++ */ ++ private void respondError(AdHocCommandData response, XMPPError.Condition condition, ++ AdHocCommand.SpecificErrorCondition specificCondition) ++ { ++ XMPPError error = new XMPPError(condition); ++ error.addExtension(new AdHocCommandData.SpecificError(specificCondition)); ++ respondError(response, error); ++ } ++ ++ /** ++ * Responds an error with an specific error. ++ * ++ * @param response the response to send. ++ * @param error the error to send. ++ */ ++ private void respondError(AdHocCommandData response, XMPPError error) { ++ response.setType(IQ.Type.ERROR); ++ response.setError(error); ++ connection.sendPacket(response); ++ } ++ ++ /** ++ * Creates a new instance of a command to be used by a new execution request ++ * ++ * @param commandNode the command node that identifies it. ++ * @param sessionID the session id of this execution. ++ * @return the command instance to execute. ++ * @throws XMPPException if there is problem creating the new instance. ++ */ ++ private LocalCommand newInstanceOfCmd(String commandNode, String sessionID) ++ throws XMPPException ++ { ++ AdHocCommandInfo commandInfo = commands.get(commandNode); ++ LocalCommand command; ++ try { ++ command = (LocalCommand) commandInfo.getCommandInstance(); ++ command.setSessionID(sessionID); ++ command.setName(commandInfo.getName()); ++ command.setNode(commandInfo.getNode()); ++ } ++ catch (InstantiationException e) { ++ e.printStackTrace(); ++ throw new XMPPException(new XMPPError( ++ XMPPError.Condition.interna_server_error)); ++ } ++ catch (IllegalAccessException e) { ++ e.printStackTrace(); ++ throw new XMPPException(new XMPPError( ++ XMPPError.Condition.interna_server_error)); ++ } ++ return command; ++ } ++ ++ /** ++ * Returns the registered commands of this command manager, which is related ++ * to a connection. ++ * ++ * @return the registered commands. ++ */ ++ private Collection<AdHocCommandInfo> getRegisteredCommands() { ++ return commands.values(); ++ } ++ ++ /** ++ * Stores ad-hoc command information. ++ */ ++ private static class AdHocCommandInfo { ++ ++ private String node; ++ private String name; ++ private String ownerJID; ++ private LocalCommandFactory factory; ++ ++ public AdHocCommandInfo(String node, String name, String ownerJID, ++ LocalCommandFactory factory) ++ { ++ this.node = node; ++ this.name = name; ++ this.ownerJID = ownerJID; ++ this.factory = factory; ++ } ++ ++ public LocalCommand getCommandInstance() throws InstantiationException, ++ IllegalAccessException ++ { ++ return factory.getInstance(); ++ } ++ ++ public String getName() { ++ return name; ++ } ++ ++ public String getNode() { ++ return node; ++ } ++ ++ public String getOwnerJID() { ++ return ownerJID; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/AdHocCommandNote.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/AdHocCommandNote.java +new file mode 100755 +index 0000000..d3f487f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/AdHocCommandNote.java +@@ -0,0 +1,86 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2005-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.commands; ++ ++/** ++ * Notes can be added to a command execution response. A note has a type and value. ++ * ++ * @author Gabriel Guardincerri ++ */ ++public class AdHocCommandNote { ++ ++ private Type type; ++ private String value; ++ ++ /** ++ * Creates a new adhoc command note with the specified type and value. ++ * ++ * @param type the type of the note. ++ * @param value the value of the note. ++ */ ++ public AdHocCommandNote(Type type, String value) { ++ this.type = type; ++ this.value = value; ++ } ++ ++ /** ++ * Returns the value or message of the note. ++ * ++ * @return the value or message of the note. ++ */ ++ public String getValue() { ++ return value; ++ } ++ ++ /** ++ * Return the type of the note. ++ * ++ * @return the type of the note. ++ */ ++ public Type getType() { ++ return type; ++ } ++ ++ /** ++ * Represents a note type. ++ */ ++ public enum Type { ++ ++ /** ++ * The note is informational only. This is not really an exceptional ++ * condition. ++ */ ++ info, ++ ++ /** ++ * The note indicates a warning. Possibly due to illogical (yet valid) ++ * data. ++ */ ++ warn, ++ ++ /** ++ * The note indicates an error. The text should indicate the reason for ++ * the error. ++ */ ++ error ++ } ++ ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/LocalCommand.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/LocalCommand.java +new file mode 100755 +index 0000000..36d5154 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/LocalCommand.java +@@ -0,0 +1,169 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2005-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.commands; ++ ++import org.jivesoftware.smackx.packet.AdHocCommandData; ++ ++/** ++ * Represents a command that can be executed locally from a remote location. This ++ * class must be extended to implement an specific ad-hoc command. This class ++ * provides some useful tools:<ul> ++ * <li>Node</li> ++ * <li>Name</li> ++ * <li>Session ID</li> ++ * <li>Current Stage</li> ++ * <li>Available actions</li> ++ * <li>Default action</li> ++ * </ul><p/> ++ * To implement a new command extend this class and implement all the abstract ++ * methods. When implementing the actions remember that they could be invoked ++ * several times, and that you must use the current stage number to know what to ++ * do. ++ * ++ * @author Gabriel Guardincerri ++ */ ++public abstract class LocalCommand extends AdHocCommand { ++ ++ /** ++ * The time stamp of first invokation of the command. Used to implement the session timeout. ++ */ ++ private long creationDate; ++ ++ /** ++ * The unique ID of the execution of the command. ++ */ ++ private String sessionID; ++ ++ /** ++ * The full JID of the host of the command. ++ */ ++ private String ownerJID; ++ ++ /** ++ * The number of the current stage. ++ */ ++ private int currenStage; ++ ++ public LocalCommand() { ++ super(); ++ this.creationDate = System.currentTimeMillis(); ++ currenStage = -1; ++ } ++ ++ /** ++ * The sessionID is an unique identifier of an execution request. This is ++ * automatically handled and should not be called. ++ * ++ * @param sessionID the unique session id of this execution ++ */ ++ public void setSessionID(String sessionID) { ++ this.sessionID = sessionID; ++ getData().setSessionID(sessionID); ++ } ++ ++ /** ++ * Returns the session ID of this execution. ++ * ++ * @return the unique session id of this execution ++ */ ++ public String getSessionID() { ++ return sessionID; ++ } ++ ++ /** ++ * Sets the JID of the command host. This is automatically handled and should ++ * not be called. ++ * ++ * @param ownerJID the JID of the owner. ++ */ ++ public void setOwnerJID(String ownerJID) { ++ this.ownerJID = ownerJID; ++ } ++ ++ @Override ++ public String getOwnerJID() { ++ return ownerJID; ++ } ++ ++ /** ++ * Returns the date the command was created. ++ * ++ * @return the date the command was created. ++ */ ++ public long getCreationDate() { ++ return creationDate; ++ } ++ ++ /** ++ * Returns true if the current stage is the last one. If it is then the ++ * execution of some action will complete the execution of the command. ++ * Commands that don't have multiple stages can always return <tt>true</tt>. ++ * ++ * @return true if the command is in the last stage. ++ */ ++ public abstract boolean isLastStage(); ++ ++ /** ++ * Returns true if the specified requester has permission to execute all the ++ * stages of this action. This is checked when the first request is received, ++ * if the permission is grant then the requester will be able to execute ++ * all the stages of the command. It is not checked again during the ++ * execution. ++ * ++ * @param jid the JID to check permissions on. ++ * @return true if the user has permission to execute this action. ++ */ ++ public abstract boolean hasPermission(String jid); ++ ++ /** ++ * Returns the currently executing stage number. The first stage number is ++ * 0. During the execution of the first action this method will answer 0. ++ * ++ * @return the current stage number. ++ */ ++ public int getCurrentStage() { ++ return currenStage; ++ } ++ ++ @Override ++ void setData(AdHocCommandData data) { ++ data.setSessionID(sessionID); ++ super.setData(data); ++ } ++ ++ /** ++ * Increase the current stage number. This is automatically handled and should ++ * not be called. ++ * ++ */ ++ void incrementStage() { ++ currenStage++; ++ } ++ ++ /** ++ * Decrease the current stage number. This is automatically handled and should ++ * not be called. ++ * ++ */ ++ void decrementStage() { ++ currenStage--; ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/LocalCommandFactory.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/LocalCommandFactory.java +new file mode 100644 +index 0000000..11efd2b +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/LocalCommandFactory.java +@@ -0,0 +1,24 @@ ++package org.jivesoftware.smackx.commands; ++ ++/** ++ * A factory for creating local commands. It's useful in cases where instantiation ++ * of a command is more complicated than just using the default constructor. For example, ++ * when arguments must be passed into the constructor or when using a dependency injection ++ * framework. When a LocalCommandFactory isn't used, you can provide the AdHocCommandManager ++ * a Class object instead. For more details, see ++ * {@link AdHocCommandManager#registerCommand(String, String, LocalCommandFactory)}. ++ * ++ * @author Matt Tucker ++ */ ++public interface LocalCommandFactory { ++ ++ /** ++ * Returns an instance of a LocalCommand. ++ * ++ * @return a LocalCommand instance. ++ * @throws InstantiationException if creating an instance failed. ++ * @throws IllegalAccessException if creating an instance is not allowed. ++ */ ++ public LocalCommand getInstance() throws InstantiationException, IllegalAccessException; ++ ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/RemoteCommand.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/RemoteCommand.java +new file mode 100755 +index 0000000..a32d267 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/RemoteCommand.java +@@ -0,0 +1,201 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2005-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.commands; ++ ++import org.jivesoftware.smack.PacketCollector; ++import org.jivesoftware.smack.SmackConfiguration; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.packet.AdHocCommandData; ++ ++/** ++ * Represents a command that is in a remote location. Invoking one of the ++ * {@link AdHocCommand.Action#execute execute}, {@link AdHocCommand.Action#next next}, ++ * {@link AdHocCommand.Action#prev prev}, {@link AdHocCommand.Action#cancel cancel} or ++ * {@link AdHocCommand.Action#complete complete} actions results in executing that ++ * action in the remote location. In response to that action the internal state ++ * of the this command instance will change. For example, if the command is a ++ * single stage command, then invoking the execute action will execute this ++ * action in the remote location. After that the local instance will have a ++ * state of "completed" and a form or notes that applies. ++ * ++ * @author Gabriel Guardincerri ++ * ++ */ ++public class RemoteCommand extends AdHocCommand { ++ ++ /** ++ * The connection that is used to execute this command ++ */ ++ private Connection connection; ++ ++ /** ++ * The full JID of the command host ++ */ ++ private String jid; ++ ++ /** ++ * The session ID of this execution. ++ */ ++ private String sessionID; ++ ++ ++ /** ++ * The number of milliseconds to wait for a response from the server ++ * The default value is the default packet reply timeout (5000 ms). ++ */ ++ private long packetReplyTimeout; ++ ++ /** ++ * Creates a new RemoteCommand that uses an specific connection to execute a ++ * command identified by <code>node</code> in the host identified by ++ * <code>jid</code> ++ * ++ * @param connection the connection to use for the execution. ++ * @param node the identifier of the command. ++ * @param jid the JID of the host. ++ */ ++ protected RemoteCommand(Connection connection, String node, String jid) { ++ super(); ++ this.connection = connection; ++ this.jid = jid; ++ this.setNode(node); ++ this.packetReplyTimeout = SmackConfiguration.getPacketReplyTimeout(); ++ } ++ ++ @Override ++ public void cancel() throws XMPPException { ++ executeAction(Action.cancel, packetReplyTimeout); ++ } ++ ++ @Override ++ public void complete(Form form) throws XMPPException { ++ executeAction(Action.complete, form, packetReplyTimeout); ++ } ++ ++ @Override ++ public void execute() throws XMPPException { ++ executeAction(Action.execute, packetReplyTimeout); ++ } ++ ++ /** ++ * Executes the default action of the command with the information provided ++ * in the Form. This form must be the anwser form of the previous stage. If ++ * there is a problem executing the command it throws an XMPPException. ++ * ++ * @param form the form anwser of the previous stage. ++ * @throws XMPPException if an error occurs. ++ */ ++ public void execute(Form form) throws XMPPException { ++ executeAction(Action.execute, form, packetReplyTimeout); ++ } ++ ++ @Override ++ public void next(Form form) throws XMPPException { ++ executeAction(Action.next, form, packetReplyTimeout); ++ } ++ ++ @Override ++ public void prev() throws XMPPException { ++ executeAction(Action.prev, packetReplyTimeout); ++ } ++ ++ private void executeAction(Action action, long packetReplyTimeout) throws XMPPException { ++ executeAction(action, null, packetReplyTimeout); ++ } ++ ++ /** ++ * Executes the <code>action</codo> with the <code>form</code>. ++ * The action could be any of the available actions. The form must ++ * be the anwser of the previous stage. It can be <tt>null</tt> if it is the first stage. ++ * ++ * @param action the action to execute. ++ * @param form the form with the information. ++ * @param timeout the amount of time to wait for a reply. ++ * @throws XMPPException if there is a problem executing the command. ++ */ ++ private void executeAction(Action action, Form form, long timeout) throws XMPPException { ++ // TODO: Check that all the required fields of the form were filled, if ++ // TODO: not throw the corresponding exeption. This will make a faster response, ++ // TODO: since the request is stoped before it's sent. ++ AdHocCommandData data = new AdHocCommandData(); ++ data.setType(IQ.Type.SET); ++ data.setTo(getOwnerJID()); ++ data.setNode(getNode()); ++ data.setSessionID(sessionID); ++ data.setAction(action); ++ ++ if (form != null) { ++ data.setForm(form.getDataFormToSend()); ++ } ++ ++ PacketCollector collector = connection.createPacketCollector( ++ new PacketIDFilter(data.getPacketID())); ++ ++ connection.sendPacket(data); ++ ++ Packet response = collector.nextResult(timeout); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ ++ AdHocCommandData responseData = (AdHocCommandData) response; ++ this.sessionID = responseData.getSessionID(); ++ super.setData(responseData); ++ } ++ ++ @Override ++ public String getOwnerJID() { ++ return jid; ++ } ++ ++ /** ++ * Returns the number of milliseconds to wait for a respone. The ++ * {@link SmackConfiguration#getPacketReplyTimeout default} value ++ * should be adjusted for commands that can take a long time to execute. ++ * ++ * @return the number of milliseconds to wait for responses. ++ */ ++ public long getPacketReplyTimeout() { ++ return packetReplyTimeout; ++ } ++ ++ /** ++ * Returns the number of milliseconds to wait for a respone. The ++ * {@link SmackConfiguration#getPacketReplyTimeout default} value ++ * should be adjusted for commands that can take a long time to execute. ++ * ++ * @param packetReplyTimeout the number of milliseconds to wait for responses. ++ */ ++ public void setPacketReplyTimeout(long packetReplyTimeout) { ++ this.packetReplyTimeout = packetReplyTimeout; ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/debugger/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smackx/debugger/package.html +new file mode 100644 +index 0000000..8ea20e0 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/debugger/package.html +@@ -0,0 +1 @@ ++<body>Smack optional Debuggers.</body> +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/CapsPacketListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/CapsPacketListener.java +new file mode 100644 +index 0000000..a29de33 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/CapsPacketListener.java +@@ -0,0 +1,24 @@ ++package org.jivesoftware.smackx.entitycaps; ++ ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smackx.packet.CapsExtension; ++ ++class CapsPacketListener implements PacketListener { ++ ++ private EntityCapsManager manager; ++ ++ protected CapsPacketListener(EntityCapsManager manager) { ++ this.manager = manager; ++ } ++ ++ public void processPacket(Packet packet) { ++ CapsExtension ext = ++ (CapsExtension) packet.getExtension(CapsExtension.NODE_NAME, CapsExtension.XMLNS); ++ ++ String nodeVer = ext.getNode() + "#" + ext.getVersion(); ++ String user = packet.getFrom(); ++ ++ manager.addUserCapsNode(user, nodeVer); ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/CapsVerListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/CapsVerListener.java +new file mode 100644 +index 0000000..2a72dbb +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/CapsVerListener.java +@@ -0,0 +1,5 @@ ++package org.jivesoftware.smackx.entitycaps; ++ ++public interface CapsVerListener { ++ public void capsVerUpdated(String capsVer); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/EntityCapsManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/EntityCapsManager.java +new file mode 100644 +index 0000000..036c7c7 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/EntityCapsManager.java +@@ -0,0 +1,326 @@ ++/* ++ * Copyright 2009 Jonas Ã…dahl. ++ * Copyright 2011 Florian Schmaus ++ * ++ * All rights reserved. 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.smackx.entitycaps; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.packet.Presence; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.AndFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.filter.PacketExtensionFilter; ++import org.jivesoftware.smack.provider.ProviderManager; ++import org.jivesoftware.smack.util.Base64; ++import org.jivesoftware.smackx.FormField; ++import org.jivesoftware.smackx.ServiceDiscoveryManager; ++import org.jivesoftware.smackx.ServiceDiscoveryManagerInterface; ++import org.jivesoftware.smackx.provider.CapsExtensionProvider; ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++import org.jivesoftware.smackx.packet.CapsExtension; ++import org.jivesoftware.smackx.packet.DataForm; ++import org.jivesoftware.smackx.packet.DiscoverInfo.Feature; ++ ++import java.util.Comparator; ++import java.util.Iterator; ++import java.util.Map; ++import java.util.Set; ++import java.util.SortedSet; ++import java.util.TreeSet; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.CopyOnWriteArraySet; ++import java.security.MessageDigest; ++import java.security.NoSuchAlgorithmException; ++ ++ ++/** ++ * Keeps track of entity capabilities. ++ */ ++public class EntityCapsManager { ++ ++ public static final String HASH_METHOD = "sha-1"; ++ public static final String HASH_METHOD_CAPS = "SHA-1"; ++ ++ // TODO entityNode should become a constant (final) ++ private static String entityNode = "http://www.igniterealtime.org/projects/smack/"; ++ private static EntityCapsPersistentCache persistentCache; ++ ++ /** ++ * Map of (node, hash algorithm) -> DiscoverInfo. ++ */ ++ private static Map<String,DiscoverInfo> caps = ++ new ConcurrentHashMap<String,DiscoverInfo>(); ++ ++ /** ++ * Map of Full JID -> DiscoverInfo/null. ++ * In case of c2s connection the key is formed as user@server/resource (resource is required) ++ * In case of link-local connection the key is formed as user@host (no resource) ++ * In case of a server or component the key is formed as domain ++ */ ++ private Map<String,String> userCaps = ++ new ConcurrentHashMap<String,String>(); ++ ++ // CapsVerListeners gets notified when the version string is changed. ++ private Set<CapsVerListener> capsVerListeners = ++ new CopyOnWriteArraySet<CapsVerListener>(); ++ ++ private String currentCapsVersion = null; ++ ++ static { ++ ProviderManager.getInstance().addExtensionProvider(CapsExtension.NODE_NAME, ++ CapsExtension.XMLNS, new CapsExtensionProvider()); ++ } ++ ++ /** ++ * Add DiscoverInfo to the database. ++ * ++ * @param node The node name. Could be for example "http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w=". ++ * @param info DiscoverInfo for the specified node. ++ */ ++ public static void addDiscoverInfoByNode(String node, DiscoverInfo info) { ++ cleanupDicsoverInfo(info); ++ ++ caps.put(node, info); ++ ++ if (persistentCache != null) ++ persistentCache.addDiscoverInfoByNodePersistent(node, info); ++ } ++ ++ public EntityCapsManager(ServiceDiscoveryManagerInterface sdm) { ++ // Add Entity Capabilities (XEP-0115) feature node. ++ sdm.addFeature("http://jabber.org/protocol/caps"); ++ } ++ ++ /** ++ * Add a record telling what entity caps node a user has. The entity caps ++ * node has the format node#ver. ++ * ++ * @param user the user (Full JID) ++ * @param node the entity caps node#ver ++ */ ++ public void addUserCapsNode(String user, String node) { ++ if (user != null && node != null) { ++ userCaps.put(user, node); ++ } ++ } ++ ++ /** ++ * Remove a record telling what entity caps node a user has. ++ * ++ * @param user the user (Full JID) ++ */ ++ public void removeUserCapsNode(String user) { ++ userCaps.remove(user); ++ } ++ ++ /** ++ * Get the Node version (node#ver) of a user. ++ * ++ * @param user the user (Full JID) ++ * @return the node version. ++ */ ++ public String getNodeVersionByUser(String user) { ++ return userCaps.get(user); ++ } ++ ++ /** ++ * Get the discover info given a user name. The discover ++ * info is returned if the user has a node#ver associated with ++ * it and the node#ver has a discover info associated with it. ++ * ++ * @param user user name (Full JID) ++ * @return the discovered info ++ */ ++ public DiscoverInfo getDiscoverInfoByUser(String user) { ++ String capsNode = userCaps.get(user); ++ if (capsNode == null) ++ return null; ++ ++ return getDiscoverInfoByNode(capsNode); ++ } ++ ++ /** ++ * Get our own caps version. ++ * ++ * @return our own caps version ++ */ ++ public String getCapsVersion() { ++ return currentCapsVersion; ++ } ++ ++ /** ++ * Get our own entity node. ++ * ++ * @return our own entity node. ++ */ ++ public String getNode() { ++ return entityNode; ++ } ++ ++ /** ++ * Set our own entity node. ++ * ++ * @param node the new node ++ */ ++ public void setNode(String node) { ++ entityNode = node; ++ } ++ ++ /** ++ * Retrieve DiscoverInfo for a specific node. ++ * ++ * @param node The node name. ++ * @return The corresponding DiscoverInfo or null if none is known. ++ */ ++ public static DiscoverInfo getDiscoverInfoByNode(String node) { ++ return caps.get(node); ++ } ++ ++ private static void cleanupDicsoverInfo(DiscoverInfo info) { ++ info.setFrom(null); ++ info.setTo(null); ++ info.setPacketID(null); ++ } ++ ++ public void addPacketListener(Connection connection) { ++ PacketFilter f = ++ new AndFilter( ++ new PacketTypeFilter(Presence.class), ++ new PacketExtensionFilter(CapsExtension.NODE_NAME, CapsExtension.XMLNS)); ++ connection.addPacketListener(new CapsPacketListener(this), f); ++ } ++ ++ public void addCapsVerListener(CapsVerListener listener) { ++ capsVerListeners.add(listener); ++ ++ if (currentCapsVersion != null) ++ listener.capsVerUpdated(currentCapsVersion); ++ } ++ ++ public void removeCapsVerListener(CapsVerListener listener) { ++ capsVerListeners.remove(listener); ++ } ++ ++ private void notifyCapsVerListeners() { ++ for (CapsVerListener listener : capsVerListeners) { ++ listener.capsVerUpdated(currentCapsVersion); ++ } ++ } ++ ++ /** ++ * Calculate Entity Caps version string ++ * ++ * @param capsString ++ * @return ++ */ ++ private static String capsToHash(String capsString) { ++ try { ++ MessageDigest md = MessageDigest.getInstance(HASH_METHOD_CAPS); ++ byte[] digest = md.digest(capsString.getBytes()); ++ return Base64.encodeBytes(digest); ++ } ++ catch (NoSuchAlgorithmException nsae) { ++ return null; ++ } ++ } ++ ++ private static String formFieldValuesToCaps(Iterator<String> i) { ++ String s = ""; ++ SortedSet<String> fvs = new TreeSet<String>(); ++ for (; i.hasNext();) { ++ fvs.add(i.next()); ++ } ++ for (String fv : fvs) { ++ s += fv + "<"; ++ } ++ return s; ++ } ++ ++ public void calculateEntityCapsVersion(DiscoverInfo discoverInfo, ++ String identityType, ++ String identityName, ++ DataForm extendedInfo) { ++ String s = ""; ++ ++ // Add identity ++ // FIXME language ++ s += "client/" + identityType + "//" + identityName + "<"; ++ ++ // Add features ++ SortedSet<String> features = new TreeSet<String>(); ++ for (Iterator<Feature> it = discoverInfo.getFeatures(); it.hasNext();) ++ features.add(it.next().getVar()); ++ ++ for (String f : features) { ++ s += f + "<"; ++ } ++ ++ if (extendedInfo != null) { ++ synchronized (extendedInfo) { ++ SortedSet<FormField> fs = new TreeSet<FormField>( ++ new Comparator<FormField>() { ++ public int compare(FormField f1, FormField f2) { ++ return f1.getVariable().compareTo(f2.getVariable()); ++ } ++ }); ++ ++ FormField ft = null; ++ ++ for (Iterator<FormField> i = extendedInfo.getFields(); i.hasNext();) { ++ FormField f = i.next(); ++ if (!f.getVariable().equals("FORM_TYPE")) { ++ fs.add(f); ++ } ++ else { ++ ft = f; ++ } ++ } ++ ++ // Add FORM_TYPE values ++ if (ft != null) { ++ s += formFieldValuesToCaps(ft.getValues()); ++ } ++ ++ // Add the other values ++ for (FormField f : fs) { ++ s += f.getVariable() + "<"; ++ s += formFieldValuesToCaps(f.getValues()); ++ } ++ } ++ } ++ ++ ++ setCurrentCapsVersion(discoverInfo, capsToHash(s)); ++ } ++ ++ /** ++ * Set our own caps version. ++ * ++ * @param capsVersion the new caps version ++ */ ++ public void setCurrentCapsVersion(DiscoverInfo discoverInfo, String capsVersion) { ++ currentCapsVersion = capsVersion; ++ addDiscoverInfoByNode(getNode() + "#" + capsVersion, discoverInfo); ++ notifyCapsVerListeners(); ++ } ++ ++ public static void setPersistentCache(EntityCapsPersistentCache cache) { ++ if (persistentCache != null) ++ throw new IllegalStateException("Entity Caps Persistent Cache was already set"); ++ persistentCache = cache; ++ persistentCache.replay(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/EntityCapsPersistentCache.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/EntityCapsPersistentCache.java +new file mode 100644 +index 0000000..6dcca6f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/EntityCapsPersistentCache.java +@@ -0,0 +1,23 @@ ++package org.jivesoftware.smackx.entitycaps; ++ ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++ ++public interface EntityCapsPersistentCache { ++ /** ++ * Add an DiscoverInfo to the persistent Cache ++ * ++ * @param node ++ * @param info ++ */ ++ abstract void addDiscoverInfoByNodePersistent(String node, DiscoverInfo info); ++ ++ /** ++ * Replay the Caches data into EntityCapsManager ++ */ ++ abstract void replay(); ++ ++ /** ++ * Empty the Cache ++ */ ++ abstract void emptyCache(); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/SimpleDirectoryPersistentCache.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/SimpleDirectoryPersistentCache.java +new file mode 100644 +index 0000000..e0a559a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/SimpleDirectoryPersistentCache.java +@@ -0,0 +1,166 @@ ++/** ++ * Copyright 2011 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.smackx.entitycaps; ++ ++ ++import java.io.DataInputStream; ++import java.io.DataOutputStream; ++import java.io.File; ++import java.io.FileInputStream; ++import java.io.FileOutputStream; ++import java.io.IOException; ++import java.io.Reader; ++import java.io.StringReader; ++ ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smack.provider.ProviderManager; ++import org.jivesoftware.smack.util.Base64; ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++import org.xmlpull.v1.XmlPullParserFactory; ++import org.xmlpull.v1.XmlPullParser; ++import org.xmlpull.v1.XmlPullParserException; ++ ++/** ++ * Simples implementation of an EntityCapsPersistentCache that uses a ++ * directory to store the Caps information for every known node. Every node ++ * is represented by an file. ++ * ++ * @author Florian Schmaus ++ * ++ */ ++public class SimpleDirectoryPersistentCache implements ++ EntityCapsPersistentCache { ++ ++ private File cacheDir; ++ ++ /** ++ * Creates a new SimpleDirectoryPersistentCache Object. Make sure that the ++ * cacheDir exists and that it's an directory. ++ * ++ * @param cacheDir ++ */ ++ public SimpleDirectoryPersistentCache(File cacheDir) { ++ if (!cacheDir.exists()) ++ throw new IllegalStateException("Cache directory \"" + cacheDir + "\" does not exist"); ++ if (!cacheDir.isDirectory()) ++ throw new IllegalStateException("Cache directory \"" + cacheDir + "\" is not a directory"); ++ ++ this.cacheDir = cacheDir; ++ } ++ ++ @Override ++ public void addDiscoverInfoByNodePersistent(String node, DiscoverInfo info) { ++ String base64Node = Base64.encodeBytes(node.getBytes()); ++ File nodeFile = new File(cacheDir, base64Node); ++ ++ try { ++ if (nodeFile.createNewFile()) ++ writeInfoToFile(nodeFile, info); ++ } catch (IOException e) { ++ e.printStackTrace(); ++ } ++ } ++ ++ @Override ++ public void replay() { ++ File[] files = cacheDir.listFiles(); ++ for (File f : files) { ++ String nodeBase64 = f.getName(); ++ String node = new String(Base64.decode(nodeBase64)); ++ DiscoverInfo info; ++ try { ++ info = restoreInfoFromFile(f); ++ } catch (IOException e) { ++ e.printStackTrace(); ++ continue; ++ } ++ if (info == null) ++ continue; ++ ++ EntityCapsManager.addDiscoverInfoByNode(node, info); ++ } ++ } ++ ++ public void emptyCache() { ++ File[] files = cacheDir.listFiles(); ++ for (File f : files) { ++ f.delete(); ++ } ++ } ++ ++ /** ++ * Writes the DiscoverInfo packet to an file ++ * ++ * @param file ++ * @param info ++ * @throws IOException ++ */ ++ private static void writeInfoToFile(File file, DiscoverInfo info) throws IOException { ++ DataOutputStream dos = new DataOutputStream(new FileOutputStream(file)); ++ dos.writeUTF(info.toXML()); ++ } ++ ++ /** ++ * Tries to restore an DiscoverInfo packet from a file. ++ * ++ * @param file ++ * @return ++ * @throws IOException ++ */ ++ private static DiscoverInfo restoreInfoFromFile(File file) throws IOException { ++ DataInputStream dis = new DataInputStream(new FileInputStream(file)); ++ String fileContent = dis.readUTF(); ++ Reader reader = new StringReader(fileContent); ++ XmlPullParser parser; ++ try { ++ parser = XmlPullParserFactory.newInstance().newPullParser(); ++ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); ++ parser.setInput(reader); ++ } catch (XmlPullParserException xppe) { ++ xppe.printStackTrace(); ++ return null; ++ } ++ ++ DiscoverInfo iqPacket; ++ IQProvider provider = (IQProvider) ProviderManager.getInstance().getIQProvider("query", "http://jabber.org/protocol/disco#info"); ++ ++ // Skip the first <iq id=....> tag ++ try { ++ parser.next(); ++ } catch (XmlPullParserException e1) { ++ e1.printStackTrace(); ++ return null; ++ } ++ ++ // Point parser to the query tag ++ try { ++ parser.next(); ++ } catch (XmlPullParserException e1) { ++ e1.printStackTrace(); ++ return null; ++ } ++ ++ try { ++ iqPacket = (DiscoverInfo) provider.parseIQ(parser); ++ } catch (Exception e) { ++ e.printStackTrace(); ++ return null; ++ } ++ ++ return iqPacket; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FaultTolerantNegotiator.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FaultTolerantNegotiator.java +new file mode 100644 +index 0000000..ef18606 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FaultTolerantNegotiator.java +@@ -0,0 +1,186 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx.filetransfer; ++ ++import org.jivesoftware.smack.PacketCollector; ++import org.jivesoftware.smack.SmackConfiguration; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.OrFilter; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smackx.packet.StreamInitiation; ++ ++import java.io.InputStream; ++import java.io.OutputStream; ++import java.util.concurrent.*; ++import java.util.List; ++import java.util.ArrayList; ++ ++ ++/** ++ * The fault tolerant negotiator takes two stream negotiators, the primary and the secondary ++ * negotiator. If the primary negotiator fails during the stream negotiaton process, the second ++ * negotiator is used. ++ */ ++public class FaultTolerantNegotiator extends StreamNegotiator { ++ ++ private StreamNegotiator primaryNegotiator; ++ private StreamNegotiator secondaryNegotiator; ++ private Connection connection; ++ private PacketFilter primaryFilter; ++ private PacketFilter secondaryFilter; ++ ++ public FaultTolerantNegotiator(Connection connection, StreamNegotiator primary, ++ StreamNegotiator secondary) { ++ this.primaryNegotiator = primary; ++ this.secondaryNegotiator = secondary; ++ this.connection = connection; ++ } ++ ++ public PacketFilter getInitiationPacketFilter(String from, String streamID) { ++ if (primaryFilter == null || secondaryFilter == null) { ++ primaryFilter = primaryNegotiator.getInitiationPacketFilter(from, streamID); ++ secondaryFilter = secondaryNegotiator.getInitiationPacketFilter(from, streamID); ++ } ++ return new OrFilter(primaryFilter, secondaryFilter); ++ } ++ ++ InputStream negotiateIncomingStream(Packet streamInitiation) throws XMPPException { ++ throw new UnsupportedOperationException("Negotiation only handled by create incoming " + ++ "stream method."); ++ } ++ ++ final Packet initiateIncomingStream(Connection connection, StreamInitiation initiation) { ++ throw new UnsupportedOperationException("Initiation handled by createIncomingStream " + ++ "method"); ++ } ++ ++ public InputStream createIncomingStream(StreamInitiation initiation) throws XMPPException { ++ PacketCollector collector = connection.createPacketCollector( ++ getInitiationPacketFilter(initiation.getFrom(), initiation.getSessionID())); ++ ++ connection.sendPacket(super.createInitiationAccept(initiation, getNamespaces())); ++ ++ ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(2); ++ CompletionService<InputStream> service ++ = new ExecutorCompletionService<InputStream>(threadPoolExecutor); ++ List<Future<InputStream>> futures = new ArrayList<Future<InputStream>>(); ++ InputStream stream = null; ++ XMPPException exception = null; ++ try { ++ futures.add(service.submit(new NegotiatorService(collector))); ++ futures.add(service.submit(new NegotiatorService(collector))); ++ ++ int i = 0; ++ while (stream == null && i < futures.size()) { ++ Future<InputStream> future; ++ try { ++ i++; ++ future = service.poll(10, TimeUnit.SECONDS); ++ } ++ catch (InterruptedException e) { ++ continue; ++ } ++ ++ if (future == null) { ++ continue; ++ } ++ ++ try { ++ stream = future.get(); ++ } ++ catch (InterruptedException e) { ++ /* Do Nothing */ ++ } ++ catch (ExecutionException e) { ++ exception = new XMPPException(e.getCause()); ++ } ++ } ++ } ++ finally { ++ for (Future<InputStream> future : futures) { ++ future.cancel(true); ++ } ++ collector.cancel(); ++ threadPoolExecutor.shutdownNow(); ++ } ++ if (stream == null) { ++ if (exception != null) { ++ throw exception; ++ } ++ else { ++ throw new XMPPException("File transfer negotiation failed."); ++ } ++ } ++ ++ return stream; ++ } ++ ++ private StreamNegotiator determineNegotiator(Packet streamInitiation) { ++ return primaryFilter.accept(streamInitiation) ? primaryNegotiator : secondaryNegotiator; ++ } ++ ++ public OutputStream createOutgoingStream(String streamID, String initiator, String target) ++ throws XMPPException { ++ OutputStream stream; ++ try { ++ stream = primaryNegotiator.createOutgoingStream(streamID, initiator, target); ++ } ++ catch (XMPPException ex) { ++ stream = secondaryNegotiator.createOutgoingStream(streamID, initiator, target); ++ } ++ ++ return stream; ++ } ++ ++ public String[] getNamespaces() { ++ String[] primary = primaryNegotiator.getNamespaces(); ++ String[] secondary = secondaryNegotiator.getNamespaces(); ++ ++ String[] namespaces = new String[primary.length + secondary.length]; ++ System.arraycopy(primary, 0, namespaces, 0, primary.length); ++ System.arraycopy(secondary, 0, namespaces, primary.length, secondary.length); ++ ++ return namespaces; ++ } ++ ++ public void cleanup() { ++ } ++ ++ private class NegotiatorService implements Callable<InputStream> { ++ ++ private PacketCollector collector; ++ ++ NegotiatorService(PacketCollector collector) { ++ this.collector = collector; ++ } ++ ++ public InputStream call() throws Exception { ++ Packet streamInitiation = collector.nextResult( ++ SmackConfiguration.getPacketReplyTimeout() * 2); ++ if (streamInitiation == null) { ++ throw new XMPPException("No response from remote client"); ++ } ++ StreamNegotiator negotiator = determineNegotiator(streamInitiation); ++ return negotiator.negotiateIncomingStream(streamInitiation); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransfer.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransfer.java +new file mode 100644 +index 0000000..f7c2ad9 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransfer.java +@@ -0,0 +1,380 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx.filetransfer; ++ ++import org.jivesoftware.smack.XMPPException; ++ ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.OutputStream; ++ ++/** ++ * Contains the generic file information and progress related to a particular ++ * file transfer. ++ * ++ * @author Alexander Wenckus ++ * ++ */ ++public abstract class FileTransfer { ++ ++ private String fileName; ++ ++ private String filePath; ++ ++ private long fileSize; ++ ++ private String peer; ++ ++ private Status status = Status.initial; ++ ++ private final Object statusMonitor = new Object(); ++ ++ protected FileTransferNegotiator negotiator; ++ ++ protected String streamID; ++ ++ protected long amountWritten = -1; ++ ++ private Error error; ++ ++ private Exception exception; ++ ++ /** ++ * Buffer size between input and output ++ */ ++ private static final int BUFFER_SIZE = 8192; ++ ++ protected FileTransfer(String peer, String streamID, ++ FileTransferNegotiator negotiator) { ++ this.peer = peer; ++ this.streamID = streamID; ++ this.negotiator = negotiator; ++ } ++ ++ protected void setFileInfo(String fileName, long fileSize) { ++ this.fileName = fileName; ++ this.fileSize = fileSize; ++ } ++ ++ protected void setFileInfo(String path, String fileName, long fileSize) { ++ this.filePath = path; ++ this.fileName = fileName; ++ this.fileSize = fileSize; ++ } ++ ++ /** ++ * Returns the size of the file being transfered. ++ * ++ * @return Returns the size of the file being transfered. ++ */ ++ public long getFileSize() { ++ return fileSize; ++ } ++ ++ /** ++ * Returns the name of the file being transfered. ++ * ++ * @return Returns the name of the file being transfered. ++ */ ++ public String getFileName() { ++ return fileName; ++ } ++ ++ /** ++ * Returns the local path of the file. ++ * ++ * @return Returns the local path of the file. ++ */ ++ public String getFilePath() { ++ return filePath; ++ } ++ ++ /** ++ * Returns the JID of the peer for this file transfer. ++ * ++ * @return Returns the JID of the peer for this file transfer. ++ */ ++ public String getPeer() { ++ return peer; ++ } ++ ++ /** ++ * Returns the progress of the file transfer as a number between 0 and 1. ++ * ++ * @return Returns the progress of the file transfer as a number between 0 ++ * and 1. ++ */ ++ public double getProgress() { ++ if (amountWritten <= 0 || fileSize <= 0) { ++ return 0; ++ } ++ return (double) amountWritten / (double) fileSize; ++ } ++ ++ /** ++ * Returns true if the transfer has been cancelled, if it has stopped because ++ * of a an error, or the transfer completed succesfully. ++ * ++ * @return Returns true if the transfer has been cancelled, if it has stopped ++ * because of a an error, or the transfer completed succesfully. ++ */ ++ public boolean isDone() { ++ return status == Status.cancelled || status == Status.error ++ || status == Status.complete || status == Status.refused; ++ } ++ ++ /** ++ * Retuns the current status of the file transfer. ++ * ++ * @return Retuns the current status of the file transfer. ++ */ ++ public Status getStatus() { ++ return status; ++ } ++ ++ protected void setError(Error type) { ++ this.error = type; ++ } ++ ++ /** ++ * When {@link #getStatus()} returns that there was an {@link Status#error} ++ * during the transfer, the type of error can be retrieved through this ++ * method. ++ * ++ * @return Returns the type of error that occured if one has occured. ++ */ ++ public Error getError() { ++ return error; ++ } ++ ++ /** ++ * If an exception occurs asynchronously it will be stored for later ++ * retrival. If there is an error there maybe an exception set. ++ * ++ * @return The exception that occured or null if there was no exception. ++ * @see #getError() ++ */ ++ public Exception getException() { ++ return exception; ++ } ++ ++ public String getStreamID() { ++ return streamID; ++ } ++ ++ /** ++ * Cancels the file transfer. ++ */ ++ public abstract void cancel(); ++ ++ protected void setException(Exception exception) { ++ this.exception = exception; ++ } ++ ++ protected void setStatus(Status status) { ++ synchronized (statusMonitor) { ++ this.status = status; ++ } ++ } ++ ++ protected boolean updateStatus(Status oldStatus, Status newStatus) { ++ synchronized (statusMonitor) { ++ if (oldStatus != status) { ++ return false; ++ } ++ status = newStatus; ++ return true; ++ } ++ } ++ ++ protected void writeToStream(final InputStream in, final OutputStream out) ++ throws XMPPException ++ { ++ final byte[] b = new byte[BUFFER_SIZE]; ++ int count = 0; ++ amountWritten = 0; ++ ++ do { ++ // write to the output stream ++ try { ++ out.write(b, 0, count); ++ } catch (IOException e) { ++ throw new XMPPException("error writing to output stream", e); ++ } ++ ++ amountWritten += count; ++ ++ // read more bytes from the input stream ++ try { ++ count = in.read(b); ++ } catch (IOException e) { ++ throw new XMPPException("error reading from input stream", e); ++ } ++ } while (count != -1 && !getStatus().equals(Status.cancelled)); ++ ++ // the connection was likely terminated abrubtly if these are not equal ++ if (!getStatus().equals(Status.cancelled) && getError() == Error.none ++ && amountWritten != fileSize) { ++ setStatus(Status.error); ++ this.error = Error.connection; ++ } ++ } ++ ++ /** ++ * A class to represent the current status of the file transfer. ++ * ++ * @author Alexander Wenckus ++ * ++ */ ++ public enum Status { ++ ++ /** ++ * An error occured during the transfer. ++ * ++ * @see FileTransfer#getError() ++ */ ++ error("Error"), ++ ++ /** ++ * The initial status of the file transfer. ++ */ ++ initial("Initial"), ++ ++ /** ++ * The file transfer is being negotiated with the peer. The party ++ * recieving the file has the option to accept or refuse a file transfer ++ * request. If they accept, then the process of stream negotiation will ++ * begin. If they refuse the file will not be transfered. ++ * ++ * @see #negotiating_stream ++ */ ++ negotiating_transfer("Negotiating Transfer"), ++ ++ /** ++ * The peer has refused the file transfer request halting the file ++ * transfer negotiation process. ++ */ ++ refused("Refused"), ++ ++ /** ++ * The stream to transfer the file is being negotiated over the chosen ++ * stream type. After the stream negotiating process is complete the ++ * status becomes negotiated. ++ * ++ * @see #negotiated ++ */ ++ negotiating_stream("Negotiating Stream"), ++ ++ /** ++ * After the stream negotitation has completed the intermediate state ++ * between the time when the negotiation is finished and the actual ++ * transfer begins. ++ */ ++ negotiated("Negotiated"), ++ ++ /** ++ * The transfer is in progress. ++ * ++ * @see FileTransfer#getProgress() ++ */ ++ in_progress("In Progress"), ++ ++ /** ++ * The transfer has completed successfully. ++ */ ++ complete("Complete"), ++ ++ /** ++ * The file transfer was canceled ++ */ ++ cancelled("Cancelled"); ++ ++ private String status; ++ ++ private Status(String status) { ++ this.status = status; ++ } ++ ++ public String toString() { ++ return status; ++ } ++ } ++ ++ /** ++ * Return the length of bytes written out to the stream. ++ * @return the amount in bytes written out. ++ */ ++ public long getAmountWritten(){ ++ return amountWritten; ++ } ++ ++ public enum Error { ++ /** ++ * No error ++ */ ++ none("No error"), ++ ++ /** ++ * The peer did not find any of the provided stream mechanisms ++ * acceptable. ++ */ ++ not_acceptable("The peer did not find any of the provided stream mechanisms acceptable."), ++ ++ /** ++ * The provided file to transfer does not exist or could not be read. ++ */ ++ bad_file("The provided file to transfer does not exist or could not be read."), ++ ++ /** ++ * The remote user did not respond or the connection timed out. ++ */ ++ no_response("The remote user did not respond or the connection timed out."), ++ ++ /** ++ * An error occured over the socket connected to send the file. ++ */ ++ connection("An error occured over the socket connected to send the file."), ++ ++ /** ++ * An error occured while sending or recieving the file ++ */ ++ stream("An error occured while sending or recieving the file."); ++ ++ private final String msg; ++ ++ private Error(String msg) { ++ this.msg = msg; ++ } ++ ++ /** ++ * Returns a String representation of this error. ++ * ++ * @return Returns a String representation of this error. ++ */ ++ public String getMessage() { ++ return msg; ++ } ++ ++ public String toString() { ++ return msg; ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferListener.java +new file mode 100644 +index 0000000..69b5c10 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferListener.java +@@ -0,0 +1,36 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx.filetransfer; ++ ++/** ++ * File transfers can cause several events to be raised. These events can be ++ * monitored through this interface. ++ * ++ * @author Alexander Wenckus ++ */ ++public interface FileTransferListener { ++ /** ++ * A request to send a file has been recieved from another user. ++ * ++ * @param request ++ * The request from the other user. ++ */ ++ public void fileTransferRequest(final FileTransferRequest request); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferManager.java +new file mode 100644 +index 0000000..25498a6 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferManager.java +@@ -0,0 +1,178 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx.filetransfer; ++ ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.filter.AndFilter; ++import org.jivesoftware.smack.filter.IQTypeFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.XMPPError; ++import org.jivesoftware.smackx.packet.StreamInitiation; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++/** ++ * The file transfer manager class handles the sending and recieving of files. ++ * To send a file invoke the {@link #createOutgoingFileTransfer(String)} method. ++ * <p> ++ * And to recieve a file add a file transfer listener to the manager. The ++ * listener will notify you when there is a new file transfer request. To create ++ * the {@link IncomingFileTransfer} object accept the transfer, or, if the ++ * transfer is not desirable reject it. ++ * ++ * @author Alexander Wenckus ++ * ++ */ ++public class FileTransferManager { ++ ++ private final FileTransferNegotiator fileTransferNegotiator; ++ ++ private List<FileTransferListener> listeners; ++ ++ private Connection connection; ++ ++ /** ++ * Creates a file transfer manager to initiate and receive file transfers. ++ * ++ * @param connection ++ * The Connection that the file transfers will use. ++ */ ++ public FileTransferManager(Connection connection) { ++ this.connection = connection; ++ this.fileTransferNegotiator = FileTransferNegotiator ++ .getInstanceFor(connection); ++ } ++ ++ /** ++ * Add a file transfer listener to listen to incoming file transfer ++ * requests. ++ * ++ * @param li ++ * The listener ++ * @see #removeFileTransferListener(FileTransferListener) ++ * @see FileTransferListener ++ */ ++ public void addFileTransferListener(final FileTransferListener li) { ++ if (listeners == null) { ++ initListeners(); ++ } ++ synchronized (this.listeners) { ++ listeners.add(li); ++ } ++ } ++ ++ private void initListeners() { ++ listeners = new ArrayList<FileTransferListener>(); ++ ++ connection.addPacketListener(new PacketListener() { ++ public void processPacket(Packet packet) { ++ fireNewRequest((StreamInitiation) packet); ++ } ++ }, new AndFilter(new PacketTypeFilter(StreamInitiation.class), ++ new IQTypeFilter(IQ.Type.SET))); ++ } ++ ++ protected void fireNewRequest(StreamInitiation initiation) { ++ FileTransferListener[] listeners = null; ++ synchronized (this.listeners) { ++ listeners = new FileTransferListener[this.listeners.size()]; ++ this.listeners.toArray(listeners); ++ } ++ FileTransferRequest request = new FileTransferRequest(this, initiation); ++ for (int i = 0; i < listeners.length; i++) { ++ listeners[i].fileTransferRequest(request); ++ } ++ } ++ ++ /** ++ * Removes a file transfer listener. ++ * ++ * @param li ++ * The file transfer listener to be removed ++ * @see FileTransferListener ++ */ ++ public void removeFileTransferListener(final FileTransferListener li) { ++ if (listeners == null) { ++ return; ++ } ++ synchronized (this.listeners) { ++ listeners.remove(li); ++ } ++ } ++ ++ /** ++ * Creates an OutgoingFileTransfer to send a file to another user. ++ * ++ * @param userID ++ * The fully qualified jabber ID with resource of the user to ++ * send the file to. ++ * @return The send file object on which the negotiated transfer can be run. ++ */ ++ public OutgoingFileTransfer createOutgoingFileTransfer(String userID) { ++// Why is this only accepting fully qualified JID? ++// if (userID == null || StringUtils.parseName(userID).length() <= 0 ++// || StringUtils.parseServer(userID).length() <= 0 ++// || StringUtils.parseResource(userID).length() <= 0) { ++// throw new IllegalArgumentException( ++// "The provided user id was not fully qualified"); ++// } ++ ++ return new OutgoingFileTransfer(connection.getUser(), userID, ++ fileTransferNegotiator.getNextStreamID(), ++ fileTransferNegotiator); ++ } ++ ++ /** ++ * When the file transfer request is acceptable, this method should be ++ * invoked. It will create an IncomingFileTransfer which allows the ++ * transmission of the file to procede. ++ * ++ * @param request ++ * The remote request that is being accepted. ++ * @return The IncomingFileTransfer which manages the download of the file ++ * from the transfer initiator. ++ */ ++ protected IncomingFileTransfer createIncomingFileTransfer( ++ FileTransferRequest request) { ++ if (request == null) { ++ throw new NullPointerException("RecieveRequest cannot be null"); ++ } ++ ++ IncomingFileTransfer transfer = new IncomingFileTransfer(request, ++ fileTransferNegotiator); ++ transfer.setFileInfo(request.getFileName(), request.getFileSize()); ++ ++ return transfer; ++ } ++ ++ protected void rejectIncomingFileTransfer(FileTransferRequest request) { ++ StreamInitiation initiation = request.getStreamInitiation(); ++ ++ IQ rejection = FileTransferNegotiator.createIQ( ++ initiation.getPacketID(), initiation.getFrom(), initiation ++ .getTo(), IQ.Type.ERROR); ++ rejection.setError(new XMPPError(XMPPError.Condition.no_acceptable)); ++ connection.sendPacket(rejection); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferNegotiator.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferNegotiator.java +new file mode 100644 +index 0000000..290d69c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferNegotiator.java +@@ -0,0 +1,485 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx.filetransfer; ++ ++import java.net.URLConnection; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import java.util.Random; ++import java.util.concurrent.ConcurrentHashMap; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.ConnectionListener; ++import org.jivesoftware.smack.PacketCollector; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.XMPPError; ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.FormField; ++import org.jivesoftware.smackx.ServiceDiscoveryManager; ++import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager; ++import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager; ++import org.jivesoftware.smackx.packet.DataForm; ++import org.jivesoftware.smackx.packet.StreamInitiation; ++ ++/** ++ * Manages the negotiation of file transfers according to JEP-0096. If a file is ++ * being sent the remote user chooses the type of stream under which the file ++ * will be sent. ++ * ++ * @author Alexander Wenckus ++ * @see <a href="http://xmpp.org/extensions/xep-0096.html">XEP-0096: SI File Transfer</a> ++ */ ++public class FileTransferNegotiator { ++ ++ // Static ++ ++ private static final String[] NAMESPACE = { ++ "http://jabber.org/protocol/si/profile/file-transfer", ++ "http://jabber.org/protocol/si"}; ++ ++ private static final Map<Connection, FileTransferNegotiator> transferObject = ++ new ConcurrentHashMap<Connection, FileTransferNegotiator>(); ++ ++ private static final String STREAM_INIT_PREFIX = "jsi_"; ++ ++ protected static final String STREAM_DATA_FIELD_NAME = "stream-method"; ++ ++ private static final Random randomGenerator = new Random(); ++ ++ /** ++ * A static variable to use only offer IBB for file transfer. It is generally recommend to only ++ * set this variable to true for testing purposes as IBB is the backup file transfer method ++ * and shouldn't be used as the only transfer method in production systems. ++ */ ++ public static boolean IBB_ONLY = (System.getProperty("ibb") != null);//true; ++ ++ /** ++ * Returns the file transfer negotiator related to a particular connection. ++ * When this class is requested on a particular connection the file transfer ++ * service is automatically enabled. ++ * ++ * @param connection The connection for which the transfer manager is desired ++ * @return The IMFileTransferManager ++ */ ++ public static FileTransferNegotiator getInstanceFor( ++ final Connection connection) { ++ if (connection == null) { ++ throw new IllegalArgumentException("Connection cannot be null"); ++ } ++ if (!connection.isConnected()) { ++ return null; ++ } ++ ++ if (transferObject.containsKey(connection)) { ++ return transferObject.get(connection); ++ } ++ else { ++ FileTransferNegotiator transfer = new FileTransferNegotiator( ++ connection); ++ setServiceEnabled(connection, true); ++ transferObject.put(connection, transfer); ++ return transfer; ++ } ++ } ++ ++ /** ++ * Enable the Jabber services related to file transfer on the particular ++ * connection. ++ * ++ * @param connection The connection on which to enable or disable the services. ++ * @param isEnabled True to enable, false to disable. ++ */ ++ public static void setServiceEnabled(final Connection connection, ++ final boolean isEnabled) { ++ ServiceDiscoveryManager manager = ServiceDiscoveryManager ++ .getInstanceFor(connection); ++ ++ List<String> namespaces = new ArrayList<String>(); ++ namespaces.addAll(Arrays.asList(NAMESPACE)); ++ namespaces.add(InBandBytestreamManager.NAMESPACE); ++ if (!IBB_ONLY) { ++ namespaces.add(Socks5BytestreamManager.NAMESPACE); ++ } ++ ++ for (String namespace : namespaces) { ++ if (isEnabled) { ++ if (!manager.includesFeature(namespace)) { ++ manager.addFeature(namespace); ++ } ++ } else { ++ manager.removeFeature(namespace); ++ } ++ } ++ ++ } ++ ++ /** ++ * Checks to see if all file transfer related services are enabled on the ++ * connection. ++ * ++ * @param connection The connection to check ++ * @return True if all related services are enabled, false if they are not. ++ */ ++ public static boolean isServiceEnabled(final Connection connection) { ++ ServiceDiscoveryManager manager = ServiceDiscoveryManager ++ .getInstanceFor(connection); ++ ++ List<String> namespaces = new ArrayList<String>(); ++ namespaces.addAll(Arrays.asList(NAMESPACE)); ++ namespaces.add(InBandBytestreamManager.NAMESPACE); ++ if (!IBB_ONLY) { ++ namespaces.add(Socks5BytestreamManager.NAMESPACE); ++ } ++ ++ for (String namespace : namespaces) { ++ if (!manager.includesFeature(namespace)) { ++ return false; ++ } ++ } ++ return true; ++ } ++ ++ /** ++ * A convenience method to create an IQ packet. ++ * ++ * @param ID The packet ID of the ++ * @param to To whom the packet is addressed. ++ * @param from From whom the packet is sent. ++ * @param type The IQ type of the packet. ++ * @return The created IQ packet. ++ */ ++ public static IQ createIQ(final String ID, final String to, ++ final String from, final IQ.Type type) { ++ IQ iqPacket = new IQ() { ++ public String getChildElementXML() { ++ return null; ++ } ++ }; ++ iqPacket.setPacketID(ID); ++ iqPacket.setTo(to); ++ iqPacket.setFrom(from); ++ iqPacket.setType(type); ++ ++ return iqPacket; ++ } ++ ++ /** ++ * Returns a collection of the supported transfer protocols. ++ * ++ * @return Returns a collection of the supported transfer protocols. ++ */ ++ public static Collection<String> getSupportedProtocols() { ++ List<String> protocols = new ArrayList<String>(); ++ protocols.add(InBandBytestreamManager.NAMESPACE); ++ if (!IBB_ONLY) { ++ protocols.add(Socks5BytestreamManager.NAMESPACE); ++ } ++ return Collections.unmodifiableList(protocols); ++ } ++ ++ // non-static ++ ++ private final Connection connection; ++ ++ private final StreamNegotiator byteStreamTransferManager; ++ ++ private final StreamNegotiator inbandTransferManager; ++ ++ private FileTransferNegotiator(final Connection connection) { ++ configureConnection(connection); ++ ++ this.connection = connection; ++ byteStreamTransferManager = new Socks5TransferNegotiator(connection); ++ inbandTransferManager = new IBBTransferNegotiator(connection); ++ } ++ ++ private void configureConnection(final Connection connection) { ++ connection.addConnectionListener(new ConnectionListener() { ++ public void connectionClosed() { ++ cleanup(connection); ++ } ++ ++ public void connectionClosedOnError(Exception e) { ++ cleanup(connection); ++ } ++ ++ public void reconnectionFailed(Exception e) { ++ // ignore ++ } ++ ++ public void reconnectionSuccessful() { ++ // ignore ++ } ++ ++ public void reconnectingIn(int seconds) { ++ // ignore ++ } ++ }); ++ } ++ ++ private void cleanup(final Connection connection) { ++ if (transferObject.remove(connection) != null) { ++ inbandTransferManager.cleanup(); ++ } ++ } ++ ++ /** ++ * Selects an appropriate stream negotiator after examining the incoming file transfer request. ++ * ++ * @param request The related file transfer request. ++ * @return The file transfer object that handles the transfer ++ * @throws XMPPException If there are either no stream methods contained in the packet, or ++ * there is not an appropriate stream method. ++ */ ++ public StreamNegotiator selectStreamNegotiator( ++ FileTransferRequest request) throws XMPPException { ++ StreamInitiation si = request.getStreamInitiation(); ++ FormField streamMethodField = getStreamMethodField(si ++ .getFeatureNegotiationForm()); ++ ++ if (streamMethodField == null) { ++ String errorMessage = "No stream methods contained in packet."; ++ XMPPError error = new XMPPError(XMPPError.Condition.bad_request, errorMessage); ++ IQ iqPacket = createIQ(si.getPacketID(), si.getFrom(), si.getTo(), ++ IQ.Type.ERROR); ++ iqPacket.setError(error); ++ connection.sendPacket(iqPacket); ++ throw new XMPPException(errorMessage, error); ++ } ++ ++ // select the appropriate protocol ++ ++ StreamNegotiator selectedStreamNegotiator; ++ try { ++ selectedStreamNegotiator = getNegotiator(streamMethodField); ++ } ++ catch (XMPPException e) { ++ IQ iqPacket = createIQ(si.getPacketID(), si.getFrom(), si.getTo(), ++ IQ.Type.ERROR); ++ iqPacket.setError(e.getXMPPError()); ++ connection.sendPacket(iqPacket); ++ throw e; ++ } ++ ++ // return the appropriate negotiator ++ ++ return selectedStreamNegotiator; ++ } ++ ++ private FormField getStreamMethodField(DataForm form) { ++ FormField field = null; ++ for (Iterator<FormField> it = form.getFields(); it.hasNext();) { ++ field = it.next(); ++ if (field.getVariable().equals(STREAM_DATA_FIELD_NAME)) { ++ break; ++ } ++ field = null; ++ } ++ return field; ++ } ++ ++ private StreamNegotiator getNegotiator(final FormField field) ++ throws XMPPException { ++ String variable; ++ boolean isByteStream = false; ++ boolean isIBB = false; ++ for (Iterator<FormField.Option> it = field.getOptions(); it.hasNext();) { ++ variable = it.next().getValue(); ++ if (variable.equals(Socks5BytestreamManager.NAMESPACE) && !IBB_ONLY) { ++ isByteStream = true; ++ } ++ else if (variable.equals(InBandBytestreamManager.NAMESPACE)) { ++ isIBB = true; ++ } ++ } ++ ++ if (!isByteStream && !isIBB) { ++ XMPPError error = new XMPPError(XMPPError.Condition.bad_request, ++ "No acceptable transfer mechanism"); ++ throw new XMPPException(error.getMessage(), error); ++ } ++ ++ //if (isByteStream && isIBB && field.getType().equals(FormField.TYPE_LIST_MULTI)) { ++ if (isByteStream && isIBB) { ++ return new FaultTolerantNegotiator(connection, ++ byteStreamTransferManager, ++ inbandTransferManager); ++ } ++ else if (isByteStream) { ++ return byteStreamTransferManager; ++ } ++ else { ++ return inbandTransferManager; ++ } ++ } ++ ++ /** ++ * Reject a stream initiation request from a remote user. ++ * ++ * @param si The Stream Initiation request to reject. ++ */ ++ public void rejectStream(final StreamInitiation si) { ++ XMPPError error = new XMPPError(XMPPError.Condition.forbidden, "Offer Declined"); ++ IQ iqPacket = createIQ(si.getPacketID(), si.getFrom(), si.getTo(), ++ IQ.Type.ERROR); ++ iqPacket.setError(error); ++ connection.sendPacket(iqPacket); ++ } ++ ++ /** ++ * Returns a new, unique, stream ID to identify a file transfer. ++ * ++ * @return Returns a new, unique, stream ID to identify a file transfer. ++ */ ++ public String getNextStreamID() { ++ StringBuilder buffer = new StringBuilder(); ++ buffer.append(STREAM_INIT_PREFIX); ++ buffer.append(Math.abs(randomGenerator.nextLong())); ++ ++ return buffer.toString(); ++ } ++ ++ /** ++ * Send a request to another user to send them a file. The other user has ++ * the option of, accepting, rejecting, or not responding to a received file ++ * transfer request. ++ * <p/> ++ * If they accept, the packet will contain the other user's chosen stream ++ * type to send the file across. The two choices this implementation ++ * provides to the other user for file transfer are <a ++ * href="http://www.jabber.org/jeps/jep-0065.html">SOCKS5 Bytestreams</a>, ++ * which is the preferred method of transfer, and <a ++ * href="http://www.jabber.org/jeps/jep-0047.html">In-Band Bytestreams</a>, ++ * which is the fallback mechanism. ++ * <p/> ++ * The other user may choose to decline the file request if they do not ++ * desire the file, their client does not support JEP-0096, or if there are ++ * no acceptable means to transfer the file. ++ * <p/> ++ * Finally, if the other user does not respond this method will return null ++ * after the specified timeout. ++ * ++ * @param userID The userID of the user to whom the file will be sent. ++ * @param streamID The unique identifier for this file transfer. ++ * @param fileName The name of this file. Preferably it should include an ++ * extension as it is used to determine what type of file it is. ++ * @param size The size, in bytes, of the file. ++ * @param desc A description of the file. ++ * @param responseTimeout The amount of time, in milliseconds, to wait for the remote ++ * user to respond. If they do not respond in time, this ++ * @return Returns the stream negotiator selected by the peer. ++ * @throws XMPPException Thrown if there is an error negotiating the file transfer. ++ */ ++ public StreamNegotiator negotiateOutgoingTransfer(final String userID, ++ final String streamID, final String fileName, final long size, ++ final String desc, int responseTimeout) throws XMPPException { ++ StreamInitiation si = new StreamInitiation(); ++ si.setSesssionID(streamID); ++ si.setMimeType(URLConnection.guessContentTypeFromName(fileName)); ++ ++ StreamInitiation.File siFile = new StreamInitiation.File(fileName, size); ++ siFile.setDesc(desc); ++ si.setFile(siFile); ++ ++ si.setFeatureNegotiationForm(createDefaultInitiationForm()); ++ ++ si.setFrom(connection.getUser()); ++ si.setTo(userID); ++ si.setType(IQ.Type.SET); ++ ++ PacketCollector collector = connection ++ .createPacketCollector(new PacketIDFilter(si.getPacketID())); ++ connection.sendPacket(si); ++ Packet siResponse = collector.nextResult(responseTimeout); ++ collector.cancel(); ++ ++ if (siResponse instanceof IQ) { ++ IQ iqResponse = (IQ) siResponse; ++ if (iqResponse.getType().equals(IQ.Type.RESULT)) { ++ StreamInitiation response = (StreamInitiation) siResponse; ++ return getOutgoingNegotiator(getStreamMethodField(response ++ .getFeatureNegotiationForm())); ++ ++ } ++ else if (iqResponse.getType().equals(IQ.Type.ERROR)) { ++ throw new XMPPException(iqResponse.getError()); ++ } ++ else { ++ throw new XMPPException("File transfer response unreadable"); ++ } ++ } ++ else { ++ return null; ++ } ++ } ++ ++ private StreamNegotiator getOutgoingNegotiator(final FormField field) ++ throws XMPPException { ++ String variable; ++ boolean isByteStream = false; ++ boolean isIBB = false; ++ for (Iterator<String> it = field.getValues(); it.hasNext();) { ++ variable = it.next(); ++ if (variable.equals(Socks5BytestreamManager.NAMESPACE) && !IBB_ONLY) { ++ isByteStream = true; ++ } ++ else if (variable.equals(InBandBytestreamManager.NAMESPACE)) { ++ isIBB = true; ++ } ++ } ++ ++ if (!isByteStream && !isIBB) { ++ XMPPError error = new XMPPError(XMPPError.Condition.bad_request, ++ "No acceptable transfer mechanism"); ++ throw new XMPPException(error.getMessage(), error); ++ } ++ ++ if (isByteStream && isIBB) { ++ return new FaultTolerantNegotiator(connection, ++ byteStreamTransferManager, inbandTransferManager); ++ } ++ else if (isByteStream) { ++ return byteStreamTransferManager; ++ } ++ else { ++ return inbandTransferManager; ++ } ++ } ++ ++ private DataForm createDefaultInitiationForm() { ++ DataForm form = new DataForm(Form.TYPE_FORM); ++ FormField field = new FormField(STREAM_DATA_FIELD_NAME); ++ field.setType(FormField.TYPE_LIST_SINGLE); ++ if (!IBB_ONLY) { ++ field.addOption(new FormField.Option(Socks5BytestreamManager.NAMESPACE)); ++ } ++ field.addOption(new FormField.Option(InBandBytestreamManager.NAMESPACE)); ++ form.addField(field); ++ return form; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferRequest.java +new file mode 100644 +index 0000000..d202dca +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferRequest.java +@@ -0,0 +1,138 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx.filetransfer; ++ ++import org.jivesoftware.smackx.packet.StreamInitiation; ++ ++/** ++ * A request to send a file recieved from another user. ++ * ++ * @author Alexander Wenckus ++ * ++ */ ++public class FileTransferRequest { ++ private final StreamInitiation streamInitiation; ++ ++ private final FileTransferManager manager; ++ ++ /** ++ * A recieve request is constructed from the Stream Initiation request ++ * received from the initator. ++ * ++ * @param manager ++ * The manager handling this file transfer ++ * ++ * @param si ++ * The Stream initiaton recieved from the initiator. ++ */ ++ public FileTransferRequest(FileTransferManager manager, StreamInitiation si) { ++ this.streamInitiation = si; ++ this.manager = manager; ++ } ++ ++ /** ++ * Returns the name of the file. ++ * ++ * @return Returns the name of the file. ++ */ ++ public String getFileName() { ++ return streamInitiation.getFile().getName(); ++ } ++ ++ /** ++ * Returns the size in bytes of the file. ++ * ++ * @return Returns the size in bytes of the file. ++ */ ++ public long getFileSize() { ++ return streamInitiation.getFile().getSize(); ++ } ++ ++ /** ++ * Returns the description of the file provided by the requestor. ++ * ++ * @return Returns the description of the file provided by the requestor. ++ */ ++ public String getDescription() { ++ return streamInitiation.getFile().getDesc(); ++ } ++ ++ /** ++ * Returns the mime-type of the file. ++ * ++ * @return Returns the mime-type of the file. ++ */ ++ public String getMimeType() { ++ return streamInitiation.getMimeType(); ++ } ++ ++ /** ++ * Returns the fully-qualified jabber ID of the user that requested this ++ * file transfer. ++ * ++ * @return Returns the fully-qualified jabber ID of the user that requested ++ * this file transfer. ++ */ ++ public String getRequestor() { ++ return streamInitiation.getFrom(); ++ } ++ ++ /** ++ * Returns the stream ID that uniquely identifies this file transfer. ++ * ++ * @return Returns the stream ID that uniquely identifies this file ++ * transfer. ++ */ ++ public String getStreamID() { ++ return streamInitiation.getSessionID(); ++ } ++ ++ /** ++ * Returns the stream initiation packet that was sent by the requestor which ++ * contains the parameters of the file transfer being transfer and also the ++ * methods available to transfer the file. ++ * ++ * @return Returns the stream initiation packet that was sent by the ++ * requestor which contains the parameters of the file transfer ++ * being transfer and also the methods available to transfer the ++ * file. ++ */ ++ protected StreamInitiation getStreamInitiation() { ++ return streamInitiation; ++ } ++ ++ /** ++ * Accepts this file transfer and creates the incoming file transfer. ++ * ++ * @return Returns the <b><i>IncomingFileTransfer</b></i> on which the ++ * file transfer can be carried out. ++ */ ++ public IncomingFileTransfer accept() { ++ return manager.createIncomingFileTransfer(this); ++ } ++ ++ /** ++ * Rejects the file transfer request. ++ */ ++ public void reject() { ++ manager.rejectIncomingFileTransfer(this); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/IBBTransferNegotiator.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/IBBTransferNegotiator.java +new file mode 100644 +index 0000000..df0f67e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/IBBTransferNegotiator.java +@@ -0,0 +1,152 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx.filetransfer; ++ ++import java.io.InputStream; ++import java.io.OutputStream; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.AndFilter; ++import org.jivesoftware.smack.filter.FromContainsFilter; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager; ++import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamRequest; ++import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamSession; ++import org.jivesoftware.smackx.bytestreams.ibb.packet.Open; ++import org.jivesoftware.smackx.packet.StreamInitiation; ++ ++/** ++ * The In-Band Bytestream file transfer method, or IBB for short, transfers the ++ * file over the same XML Stream used by XMPP. It is the fall-back mechanism in ++ * case the SOCKS5 bytestream method of transferring files is not available. ++ * ++ * @author Alexander Wenckus ++ * @author Henning Staib ++ * @see <a href="http://xmpp.org/extensions/xep-0047.html">XEP-0047: In-Band ++ * Bytestreams (IBB)</a> ++ */ ++public class IBBTransferNegotiator extends StreamNegotiator { ++ ++ private Connection connection; ++ ++ private InBandBytestreamManager manager; ++ ++ /** ++ * The default constructor for the In-Band Bytestream Negotiator. ++ * ++ * @param connection The connection which this negotiator works on. ++ */ ++ protected IBBTransferNegotiator(Connection connection) { ++ this.connection = connection; ++ this.manager = InBandBytestreamManager.getByteStreamManager(connection); ++ } ++ ++ public OutputStream createOutgoingStream(String streamID, String initiator, ++ String target) throws XMPPException { ++ InBandBytestreamSession session = this.manager.establishSession(target, streamID); ++ session.setCloseBothStreamsEnabled(true); ++ return session.getOutputStream(); ++ } ++ ++ public InputStream createIncomingStream(StreamInitiation initiation) ++ throws XMPPException { ++ /* ++ * In-Band Bytestream initiation listener must ignore next in-band ++ * bytestream request with given session ID ++ */ ++ this.manager.ignoreBytestreamRequestOnce(initiation.getSessionID()); ++ ++ Packet streamInitiation = initiateIncomingStream(this.connection, initiation); ++ return negotiateIncomingStream(streamInitiation); ++ } ++ ++ public PacketFilter getInitiationPacketFilter(String from, String streamID) { ++ /* ++ * this method is always called prior to #negotiateIncomingStream() so ++ * the In-Band Bytestream initiation listener must ignore the next ++ * In-Band Bytestream request with the given session ID ++ */ ++ this.manager.ignoreBytestreamRequestOnce(streamID); ++ ++ return new AndFilter(new FromContainsFilter(from), new IBBOpenSidFilter(streamID)); ++ } ++ ++ public String[] getNamespaces() { ++ return new String[] { InBandBytestreamManager.NAMESPACE }; ++ } ++ ++ InputStream negotiateIncomingStream(Packet streamInitiation) throws XMPPException { ++ // build In-Band Bytestream request ++ InBandBytestreamRequest request = new ByteStreamRequest(this.manager, ++ (Open) streamInitiation); ++ ++ // always accept the request ++ InBandBytestreamSession session = request.accept(); ++ session.setCloseBothStreamsEnabled(true); ++ return session.getInputStream(); ++ } ++ ++ public void cleanup() { ++ } ++ ++ /** ++ * This PacketFilter accepts an incoming In-Band Bytestream open request ++ * with a specified session ID. ++ */ ++ private static class IBBOpenSidFilter extends PacketTypeFilter { ++ ++ private String sessionID; ++ ++ public IBBOpenSidFilter(String sessionID) { ++ super(Open.class); ++ if (sessionID == null) { ++ throw new IllegalArgumentException("StreamID cannot be null"); ++ } ++ this.sessionID = sessionID; ++ } ++ ++ public boolean accept(Packet packet) { ++ if (super.accept(packet)) { ++ Open bytestream = (Open) packet; ++ ++ // packet must by of type SET and contains the given session ID ++ return this.sessionID.equals(bytestream.getSessionID()) ++ && IQ.Type.SET.equals(bytestream.getType()); ++ } ++ return false; ++ } ++ } ++ ++ /** ++ * Derive from InBandBytestreamRequest to access protected constructor. ++ */ ++ private static class ByteStreamRequest extends InBandBytestreamRequest { ++ ++ private ByteStreamRequest(InBandBytestreamManager manager, Open byteStreamRequest) { ++ super(manager, byteStreamRequest); ++ } ++ ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/IncomingFileTransfer.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/IncomingFileTransfer.java +new file mode 100644 +index 0000000..3973d44 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/IncomingFileTransfer.java +@@ -0,0 +1,215 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx.filetransfer; ++ ++import org.jivesoftware.smack.XMPPException; ++ ++import java.io.*; ++import java.util.concurrent.*; ++ ++/** ++ * An incoming file transfer is created when the ++ * {@link FileTransferManager#createIncomingFileTransfer(FileTransferRequest)} ++ * method is invoked. It is a file being sent to the local user from another ++ * user on the jabber network. There are two stages of the file transfer to be ++ * concerned with and they can be handled in different ways depending upon the ++ * method that is invoked on this class. ++ * <p/> ++ * The first way that a file is recieved is by calling the ++ * {@link #recieveFile()} method. This method, negotiates the appropriate stream ++ * method and then returns the <b><i>InputStream</b></i> to read the file ++ * data from. ++ * <p/> ++ * The second way that a file can be recieved through this class is by invoking ++ * the {@link #recieveFile(File)} method. This method returns immediatly and ++ * takes as its parameter a file on the local file system where the file ++ * recieved from the transfer will be put. ++ * ++ * @author Alexander Wenckus ++ */ ++public class IncomingFileTransfer extends FileTransfer { ++ ++ private FileTransferRequest recieveRequest; ++ ++ private InputStream inputStream; ++ ++ protected IncomingFileTransfer(FileTransferRequest request, ++ FileTransferNegotiator transferNegotiator) { ++ super(request.getRequestor(), request.getStreamID(), transferNegotiator); ++ this.recieveRequest = request; ++ } ++ ++ /** ++ * Negotiates the stream method to transfer the file over and then returns ++ * the negotiated stream. ++ * ++ * @return The negotiated InputStream from which to read the data. ++ * @throws XMPPException If there is an error in the negotiation process an exception ++ * is thrown. ++ */ ++ public InputStream recieveFile() throws XMPPException { ++ if (inputStream != null) { ++ throw new IllegalStateException("Transfer already negotiated!"); ++ } ++ ++ try { ++ inputStream = negotiateStream(); ++ } ++ catch (XMPPException e) { ++ setException(e); ++ throw e; ++ } ++ ++ return inputStream; ++ } ++ ++ /** ++ * This method negotitates the stream and then transfer's the file over the ++ * negotiated stream. The transfered file will be saved at the provided ++ * location. ++ * <p/> ++ * This method will return immedialtly, file transfer progress can be ++ * monitored through several methods: ++ * <p/> ++ * <UL> ++ * <LI>{@link FileTransfer#getStatus()} ++ * <LI>{@link FileTransfer#getProgress()} ++ * <LI>{@link FileTransfer#isDone()} ++ * </UL> ++ * ++ * @param file The location to save the file. ++ * @throws XMPPException when the file transfer fails ++ * @throws IllegalArgumentException This exception is thrown when the the provided file is ++ * either null, or cannot be written to. ++ */ ++ public void recieveFile(final File file) throws XMPPException { ++ if (file != null) { ++ if (!file.exists()) { ++ try { ++ file.createNewFile(); ++ } ++ catch (IOException e) { ++ throw new XMPPException( ++ "Could not create file to write too", e); ++ } ++ } ++ if (!file.canWrite()) { ++ throw new IllegalArgumentException("Cannot write to provided file"); ++ } ++ } ++ else { ++ throw new IllegalArgumentException("File cannot be null"); ++ } ++ ++ Thread transferThread = new Thread(new Runnable() { ++ public void run() { ++ try { ++ inputStream = negotiateStream(); ++ } ++ catch (XMPPException e) { ++ handleXMPPException(e); ++ return; ++ } ++ ++ OutputStream outputStream = null; ++ try { ++ outputStream = new FileOutputStream(file); ++ setStatus(Status.in_progress); ++ writeToStream(inputStream, outputStream); ++ } ++ catch (XMPPException e) { ++ setStatus(Status.error); ++ setError(Error.stream); ++ setException(e); ++ } ++ catch (FileNotFoundException e) { ++ setStatus(Status.error); ++ setError(Error.bad_file); ++ setException(e); ++ } ++ ++ if (getStatus().equals(Status.in_progress)) { ++ setStatus(Status.complete); ++ } ++ if (inputStream != null) { ++ try { ++ inputStream.close(); ++ } ++ catch (Throwable io) { ++ /* Ignore */ ++ } ++ } ++ if (outputStream != null) { ++ try { ++ outputStream.close(); ++ } ++ catch (Throwable io) { ++ /* Ignore */ ++ } ++ } ++ } ++ }, "File Transfer " + streamID); ++ transferThread.start(); ++ } ++ ++ private void handleXMPPException(XMPPException e) { ++ setStatus(FileTransfer.Status.error); ++ setException(e); ++ } ++ ++ private InputStream negotiateStream() throws XMPPException { ++ setStatus(Status.negotiating_transfer); ++ final StreamNegotiator streamNegotiator = negotiator ++ .selectStreamNegotiator(recieveRequest); ++ setStatus(Status.negotiating_stream); ++ FutureTask<InputStream> streamNegotiatorTask = new FutureTask<InputStream>( ++ new Callable<InputStream>() { ++ ++ public InputStream call() throws Exception { ++ return streamNegotiator ++ .createIncomingStream(recieveRequest.getStreamInitiation()); ++ } ++ }); ++ streamNegotiatorTask.run(); ++ InputStream inputStream; ++ try { ++ inputStream = streamNegotiatorTask.get(15, TimeUnit.SECONDS); ++ } ++ catch (InterruptedException e) { ++ throw new XMPPException("Interruption while executing", e); ++ } ++ catch (ExecutionException e) { ++ throw new XMPPException("Error in execution", e); ++ } ++ catch (TimeoutException e) { ++ throw new XMPPException("Request timed out", e); ++ } ++ finally { ++ streamNegotiatorTask.cancel(true); ++ } ++ setStatus(Status.negotiated); ++ return inputStream; ++ } ++ ++ public void cancel() { ++ setStatus(Status.cancelled); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/OutgoingFileTransfer.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/OutgoingFileTransfer.java +new file mode 100644 +index 0000000..0bc1d77 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/OutgoingFileTransfer.java +@@ -0,0 +1,453 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx.filetransfer; ++ ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.packet.XMPPError; ++ ++import java.io.*; ++ ++/** ++ * Handles the sending of a file to another user. File transfer's in jabber have ++ * several steps and there are several methods in this class that handle these ++ * steps differently. ++ * ++ * @author Alexander Wenckus ++ * ++ */ ++public class OutgoingFileTransfer extends FileTransfer { ++ ++ private static int RESPONSE_TIMEOUT = 60 * 1000; ++ private NegotiationProgress callback; ++ ++ /** ++ * Returns the time in milliseconds after which the file transfer ++ * negotiation process will timeout if the other user has not responded. ++ * ++ * @return Returns the time in milliseconds after which the file transfer ++ * negotiation process will timeout if the remote user has not ++ * responded. ++ */ ++ public static int getResponseTimeout() { ++ return RESPONSE_TIMEOUT; ++ } ++ ++ /** ++ * Sets the time in milliseconds after which the file transfer negotiation ++ * process will timeout if the other user has not responded. ++ * ++ * @param responseTimeout ++ * The timeout time in milliseconds. ++ */ ++ public static void setResponseTimeout(int responseTimeout) { ++ RESPONSE_TIMEOUT = responseTimeout; ++ } ++ ++ private OutputStream outputStream; ++ ++ private String initiator; ++ ++ private Thread transferThread; ++ ++ protected OutgoingFileTransfer(String initiator, String target, ++ String streamID, FileTransferNegotiator transferNegotiator) { ++ super(target, streamID, transferNegotiator); ++ this.initiator = initiator; ++ } ++ ++ protected void setOutputStream(OutputStream stream) { ++ if (outputStream == null) { ++ this.outputStream = stream; ++ } ++ } ++ ++ /** ++ * Returns the output stream connected to the peer to transfer the file. It ++ * is only available after it has been succesfully negotiated by the ++ * {@link StreamNegotiator}. ++ * ++ * @return Returns the output stream connected to the peer to transfer the ++ * file. ++ */ ++ protected OutputStream getOutputStream() { ++ if (getStatus().equals(FileTransfer.Status.negotiated)) { ++ return outputStream; ++ } else { ++ return null; ++ } ++ } ++ ++ /** ++ * This method handles the negotiation of the file transfer and the stream, ++ * it only returns the created stream after the negotiation has been completed. ++ * ++ * @param fileName ++ * The name of the file that will be transmitted. It is ++ * preferable for this name to have an extension as it will be ++ * used to determine the type of file it is. ++ * @param fileSize ++ * The size in bytes of the file that will be transmitted. ++ * @param description ++ * A description of the file that will be transmitted. ++ * @return The OutputStream that is connected to the peer to transmit the ++ * file. ++ * @throws XMPPException ++ * Thrown if an error occurs during the file transfer ++ * negotiation process. ++ */ ++ public synchronized OutputStream sendFile(String fileName, long fileSize, ++ String description) throws XMPPException { ++ if (isDone() || outputStream != null) { ++ throw new IllegalStateException( ++ "The negotation process has already" ++ + " been attempted on this file transfer"); ++ } ++ try { ++ this.outputStream = negotiateStream(fileName, fileSize, description); ++ } catch (XMPPException e) { ++ handleXMPPException(e); ++ throw e; ++ } ++ return outputStream; ++ } ++ ++ /** ++ * This methods handles the transfer and stream negotiation process. It ++ * returns immediately and its progress will be updated through the ++ * {@link NegotiationProgress} callback. ++ * ++ * @param fileName ++ * The name of the file that will be transmitted. It is ++ * preferable for this name to have an extension as it will be ++ * used to determine the type of file it is. ++ * @param fileSize ++ * The size in bytes of the file that will be transmitted. ++ * @param description ++ * A description of the file that will be transmitted. ++ * @param progress ++ * A callback to monitor the progress of the file transfer ++ * negotiation process and to retrieve the OutputStream when it ++ * is complete. ++ */ ++ public synchronized void sendFile(final String fileName, ++ final long fileSize, final String description, ++ final NegotiationProgress progress) ++ { ++ if(progress == null) { ++ throw new IllegalArgumentException("Callback progress cannot be null."); ++ } ++ checkTransferThread(); ++ if (isDone() || outputStream != null) { ++ throw new IllegalStateException( ++ "The negotation process has already" ++ + " been attempted for this file transfer"); ++ } ++ this.callback = progress; ++ transferThread = new Thread(new Runnable() { ++ public void run() { ++ try { ++ OutgoingFileTransfer.this.outputStream = negotiateStream( ++ fileName, fileSize, description); ++ progress.outputStreamEstablished(OutgoingFileTransfer.this.outputStream); ++ } ++ catch (XMPPException e) { ++ handleXMPPException(e); ++ } ++ } ++ }, "File Transfer Negotiation " + streamID); ++ transferThread.start(); ++ } ++ ++ private void checkTransferThread() { ++ if (transferThread != null && transferThread.isAlive() || isDone()) { ++ throw new IllegalStateException( ++ "File transfer in progress or has already completed."); ++ } ++ } ++ ++ /** ++ * This method handles the stream negotiation process and transmits the file ++ * to the remote user. It returns immediatly and the progress of the file ++ * transfer can be monitored through several methods: ++ * ++ * <UL> ++ * <LI>{@link FileTransfer#getStatus()} ++ * <LI>{@link FileTransfer#getProgress()} ++ * <LI>{@link FileTransfer#isDone()} ++ * </UL> ++ * ++ * @param file the file to transfer to the remote entity. ++ * @param description a description for the file to transfer. ++ * @throws XMPPException ++ * If there is an error during the negotiation process or the ++ * sending of the file. ++ */ ++ public synchronized void sendFile(final File file, final String description) ++ throws XMPPException { ++ checkTransferThread(); ++ if (file == null || !file.exists() || !file.canRead()) { ++ throw new IllegalArgumentException("Could not read file"); ++ } else { ++ setFileInfo(file.getAbsolutePath(), file.getName(), file.length()); ++ } ++ ++ transferThread = new Thread(new Runnable() { ++ public void run() { ++ try { ++ outputStream = negotiateStream(file.getName(), file ++ .length(), description); ++ } catch (XMPPException e) { ++ handleXMPPException(e); ++ return; ++ } ++ if (outputStream == null) { ++ return; ++ } ++ ++ if (!updateStatus(Status.negotiated, Status.in_progress)) { ++ return; ++ } ++ ++ InputStream inputStream = null; ++ try { ++ inputStream = new FileInputStream(file); ++ writeToStream(inputStream, outputStream); ++ } catch (FileNotFoundException e) { ++ setStatus(FileTransfer.Status.error); ++ setError(Error.bad_file); ++ setException(e); ++ } catch (XMPPException e) { ++ setStatus(FileTransfer.Status.error); ++ setException(e); ++ } finally { ++ try { ++ if (inputStream != null) { ++ inputStream.close(); ++ } ++ ++ outputStream.flush(); ++ outputStream.close(); ++ } catch (IOException e) { ++ /* Do Nothing */ ++ } ++ } ++ updateStatus(Status.in_progress, FileTransfer.Status.complete); ++ } ++ ++ }, "File Transfer " + streamID); ++ transferThread.start(); ++ } ++ ++ /** ++ * This method handles the stream negotiation process and transmits the file ++ * to the remote user. It returns immediatly and the progress of the file ++ * transfer can be monitored through several methods: ++ * ++ * <UL> ++ * <LI>{@link FileTransfer#getStatus()} ++ * <LI>{@link FileTransfer#getProgress()} ++ * <LI>{@link FileTransfer#isDone()} ++ * </UL> ++ * ++ * @param in the stream to transfer to the remote entity. ++ * @param fileName the name of the file that is transferred ++ * @param fileSize the size of the file that is transferred ++ * @param description a description for the file to transfer. ++ */ ++ public synchronized void sendStream(final InputStream in, final String fileName, final long fileSize, final String description){ ++ checkTransferThread(); ++ ++ transferThread = new Thread(new Runnable() { ++ public void run() { ++ setFileInfo(fileName, fileSize); ++ //Create packet filter ++ try { ++ outputStream = negotiateStream(fileName, fileSize, description); ++ } catch (XMPPException e) { ++ handleXMPPException(e); ++ return; ++ } catch (IllegalStateException e) { ++ setStatus(FileTransfer.Status.error); ++ setException(e); ++ } ++ if (outputStream == null) { ++ return; ++ } ++ ++ if (!updateStatus(Status.negotiated, Status.in_progress)) { ++ return; ++ } ++ try { ++ writeToStream(in, outputStream); ++ } catch (XMPPException e) { ++ setStatus(FileTransfer.Status.error); ++ setException(e); ++ } catch (IllegalStateException e) { ++ setStatus(FileTransfer.Status.error); ++ setException(e); ++ } finally { ++ try { ++ if (in != null) { ++ in.close(); ++ } ++ ++ outputStream.flush(); ++ outputStream.close(); ++ } catch (IOException e) { ++ /* Do Nothing */ ++ } ++ } ++ updateStatus(Status.in_progress, FileTransfer.Status.complete); ++ } ++ ++ }, "File Transfer " + streamID); ++ transferThread.start(); ++ } ++ ++ private void handleXMPPException(XMPPException e) { ++ XMPPError error = e.getXMPPError(); ++ if (error != null) { ++ int code = error.getCode(); ++ if (code == 403) { ++ setStatus(Status.refused); ++ return; ++ } ++ else if (code == 400) { ++ setStatus(Status.error); ++ setError(Error.not_acceptable); ++ } ++ else { ++ setStatus(FileTransfer.Status.error); ++ } ++ } ++ ++ setException(e); ++ } ++ ++ /** ++ * Returns the amount of bytes that have been sent for the file transfer. Or ++ * -1 if the file transfer has not started. ++ * <p> ++ * Note: This method is only useful when the {@link #sendFile(File, String)} ++ * method is called, as it is the only method that actualy transmits the ++ * file. ++ * ++ * @return Returns the amount of bytes that have been sent for the file ++ * transfer. Or -1 if the file transfer has not started. ++ */ ++ public long getBytesSent() { ++ return amountWritten; ++ } ++ ++ private OutputStream negotiateStream(String fileName, long fileSize, ++ String description) throws XMPPException { ++ // Negotiate the file transfer profile ++ ++ if (!updateStatus(Status.initial, Status.negotiating_transfer)) { ++ throw new XMPPException("Illegal state change"); ++ } ++ StreamNegotiator streamNegotiator = negotiator.negotiateOutgoingTransfer( ++ getPeer(), streamID, fileName, fileSize, description, ++ RESPONSE_TIMEOUT); ++ ++ if (streamNegotiator == null) { ++ setStatus(Status.error); ++ setError(Error.no_response); ++ return null; ++ } ++ ++ // Negotiate the stream ++ if (!updateStatus(Status.negotiating_transfer, Status.negotiating_stream)) { ++ throw new XMPPException("Illegal state change"); ++ } ++ outputStream = streamNegotiator.createOutgoingStream(streamID, ++ initiator, getPeer()); ++ ++ if (!updateStatus(Status.negotiating_stream, Status.negotiated)) { ++ throw new XMPPException("Illegal state change"); ++ } ++ return outputStream; ++ } ++ ++ public void cancel() { ++ setStatus(Status.cancelled); ++ } ++ ++ @Override ++ protected boolean updateStatus(Status oldStatus, Status newStatus) { ++ boolean isUpdated = super.updateStatus(oldStatus, newStatus); ++ if(callback != null && isUpdated) { ++ callback.statusUpdated(oldStatus, newStatus); ++ } ++ return isUpdated; ++ } ++ ++ @Override ++ protected void setStatus(Status status) { ++ Status oldStatus = getStatus(); ++ super.setStatus(status); ++ if(callback != null) { ++ callback.statusUpdated(oldStatus, status); ++ } ++ } ++ ++ @Override ++ protected void setException(Exception exception) { ++ super.setException(exception); ++ if(callback != null) { ++ callback.errorEstablishingStream(exception); ++ } ++ } ++ ++ /** ++ * A callback class to retrive the status of an outgoing transfer ++ * negotiation process. ++ * ++ * @author Alexander Wenckus ++ * ++ */ ++ public interface NegotiationProgress { ++ ++ /** ++ * Called when the status changes ++ * ++ * @param oldStatus the previous status of the file transfer. ++ * @param newStatus the new status of the file transfer. ++ */ ++ void statusUpdated(Status oldStatus, Status newStatus); ++ ++ /** ++ * Once the negotiation process is completed the output stream can be ++ * retrieved. ++ * ++ * @param stream the established stream which can be used to transfer the file to the remote ++ * entity ++ */ ++ void outputStreamEstablished(OutputStream stream); ++ ++ /** ++ * Called when an exception occurs during the negotiation progress. ++ * ++ * @param e the exception that occured. ++ */ ++ void errorEstablishingStream(Exception e); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/Socks5TransferNegotiator.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/Socks5TransferNegotiator.java +new file mode 100644 +index 0000000..3c07fdc +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/Socks5TransferNegotiator.java +@@ -0,0 +1,164 @@ ++/** ++ * All rights reserved. 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.smackx.filetransfer; ++ ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.OutputStream; ++import java.io.PushbackInputStream; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.AndFilter; ++import org.jivesoftware.smack.filter.FromMatchesFilter; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager; ++import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamRequest; ++import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamSession; ++import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; ++import org.jivesoftware.smackx.packet.StreamInitiation; ++ ++/** ++ * Negotiates a SOCKS5 Bytestream to be used for file transfers. The implementation is based on the ++ * {@link Socks5BytestreamManager} and the {@link Socks5BytestreamRequest}. ++ * ++ * @author Henning Staib ++ * @see <a href="http://xmpp.org/extensions/xep-0065.html">XEP-0065: SOCKS5 Bytestreams</a> ++ */ ++public class Socks5TransferNegotiator extends StreamNegotiator { ++ ++ private Connection connection; ++ ++ private Socks5BytestreamManager manager; ++ ++ Socks5TransferNegotiator(Connection connection) { ++ this.connection = connection; ++ this.manager = Socks5BytestreamManager.getBytestreamManager(this.connection); ++ } ++ ++ @Override ++ public OutputStream createOutgoingStream(String streamID, String initiator, String target) ++ throws XMPPException { ++ try { ++ return this.manager.establishSession(target, streamID).getOutputStream(); ++ } ++ catch (IOException e) { ++ throw new XMPPException("error establishing SOCKS5 Bytestream", e); ++ } ++ catch (InterruptedException e) { ++ throw new XMPPException("error establishing SOCKS5 Bytestream", e); ++ } ++ } ++ ++ @Override ++ public InputStream createIncomingStream(StreamInitiation initiation) throws XMPPException, ++ InterruptedException { ++ /* ++ * SOCKS5 initiation listener must ignore next SOCKS5 Bytestream request with given session ++ * ID ++ */ ++ this.manager.ignoreBytestreamRequestOnce(initiation.getSessionID()); ++ ++ Packet streamInitiation = initiateIncomingStream(this.connection, initiation); ++ return negotiateIncomingStream(streamInitiation); ++ } ++ ++ @Override ++ public PacketFilter getInitiationPacketFilter(final String from, String streamID) { ++ /* ++ * this method is always called prior to #negotiateIncomingStream() so the SOCKS5 ++ * InitiationListener must ignore the next SOCKS5 Bytestream request with the given session ++ * ID ++ */ ++ this.manager.ignoreBytestreamRequestOnce(streamID); ++ ++ return new AndFilter(new FromMatchesFilter(from), new BytestreamSIDFilter(streamID)); ++ } ++ ++ @Override ++ public String[] getNamespaces() { ++ return new String[] { Socks5BytestreamManager.NAMESPACE }; ++ } ++ ++ @Override ++ InputStream negotiateIncomingStream(Packet streamInitiation) throws XMPPException, ++ InterruptedException { ++ // build SOCKS5 Bytestream request ++ Socks5BytestreamRequest request = new ByteStreamRequest(this.manager, ++ (Bytestream) streamInitiation); ++ ++ // always accept the request ++ Socks5BytestreamSession session = request.accept(); ++ ++ // test input stream ++ try { ++ PushbackInputStream stream = new PushbackInputStream(session.getInputStream()); ++ int firstByte = stream.read(); ++ stream.unread(firstByte); ++ return stream; ++ } ++ catch (IOException e) { ++ throw new XMPPException("Error establishing input stream", e); ++ } ++ } ++ ++ @Override ++ public void cleanup() { ++ /* do nothing */ ++ } ++ ++ /** ++ * This PacketFilter accepts an incoming SOCKS5 Bytestream request with a specified session ID. ++ */ ++ private static class BytestreamSIDFilter extends PacketTypeFilter { ++ ++ private String sessionID; ++ ++ public BytestreamSIDFilter(String sessionID) { ++ super(Bytestream.class); ++ if (sessionID == null) { ++ throw new IllegalArgumentException("StreamID cannot be null"); ++ } ++ this.sessionID = sessionID; ++ } ++ ++ @Override ++ public boolean accept(Packet packet) { ++ if (super.accept(packet)) { ++ Bytestream bytestream = (Bytestream) packet; ++ ++ // packet must by of type SET and contains the given session ID ++ return this.sessionID.equals(bytestream.getSessionID()) ++ && IQ.Type.SET.equals(bytestream.getType()); ++ } ++ return false; ++ } ++ ++ } ++ ++ /** ++ * Derive from Socks5BytestreamRequest to access protected constructor. ++ */ ++ private static class ByteStreamRequest extends Socks5BytestreamRequest { ++ ++ private ByteStreamRequest(Socks5BytestreamManager manager, Bytestream byteStreamRequest) { ++ super(manager, byteStreamRequest); ++ } ++ ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/StreamNegotiator.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/StreamNegotiator.java +new file mode 100644 +index 0000000..46ece9c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/StreamNegotiator.java +@@ -0,0 +1,167 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx.filetransfer; ++ ++import org.jivesoftware.smack.PacketCollector; ++import org.jivesoftware.smack.SmackConfiguration; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.XMPPError; ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.FormField; ++import org.jivesoftware.smackx.packet.DataForm; ++import org.jivesoftware.smackx.packet.StreamInitiation; ++ ++import java.io.InputStream; ++import java.io.OutputStream; ++ ++/** ++ * After the file transfer negotiation process is completed according to ++ * JEP-0096, the negotiation process is passed off to a particular stream ++ * negotiator. The stream negotiator will then negotiate the chosen stream and ++ * return the stream to transfer the file. ++ * ++ * @author Alexander Wenckus ++ */ ++public abstract class StreamNegotiator { ++ ++ /** ++ * Creates the initiation acceptance packet to forward to the stream ++ * initiator. ++ * ++ * @param streamInitiationOffer The offer from the stream initiator to connect for a stream. ++ * @param namespaces The namespace that relates to the accepted means of transfer. ++ * @return The response to be forwarded to the initiator. ++ */ ++ public StreamInitiation createInitiationAccept( ++ StreamInitiation streamInitiationOffer, String[] namespaces) ++ { ++ StreamInitiation response = new StreamInitiation(); ++ response.setTo(streamInitiationOffer.getFrom()); ++ response.setFrom(streamInitiationOffer.getTo()); ++ response.setType(IQ.Type.RESULT); ++ response.setPacketID(streamInitiationOffer.getPacketID()); ++ ++ DataForm form = new DataForm(Form.TYPE_SUBMIT); ++ FormField field = new FormField( ++ FileTransferNegotiator.STREAM_DATA_FIELD_NAME); ++ for (String namespace : namespaces) { ++ field.addValue(namespace); ++ } ++ form.addField(field); ++ ++ response.setFeatureNegotiationForm(form); ++ return response; ++ } ++ ++ ++ public IQ createError(String from, String to, String packetID, XMPPError xmppError) { ++ IQ iq = FileTransferNegotiator.createIQ(packetID, to, from, IQ.Type.ERROR); ++ iq.setError(xmppError); ++ return iq; ++ } ++ ++ Packet initiateIncomingStream(Connection connection, StreamInitiation initiation) throws XMPPException { ++ StreamInitiation response = createInitiationAccept(initiation, ++ getNamespaces()); ++ ++ // establish collector to await response ++ PacketCollector collector = connection ++ .createPacketCollector(getInitiationPacketFilter(initiation.getFrom(), initiation.getSessionID())); ++ connection.sendPacket(response); ++ ++ Packet streamMethodInitiation = collector ++ .nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ collector.cancel(); ++ if (streamMethodInitiation == null) { ++ throw new XMPPException("No response from file transfer initiator"); ++ } ++ ++ return streamMethodInitiation; ++ } ++ ++ /** ++ * Returns the packet filter that will return the initiation packet for the appropriate stream ++ * initiation. ++ * ++ * @param from The initiator of the file transfer. ++ * @param streamID The stream ID related to the transfer. ++ * @return The <b><i>PacketFilter</b></i> that will return the packet relatable to the stream ++ * initiation. ++ */ ++ public abstract PacketFilter getInitiationPacketFilter(String from, String streamID); ++ ++ ++ abstract InputStream negotiateIncomingStream(Packet streamInitiation) throws XMPPException, ++ InterruptedException; ++ ++ /** ++ * This method handles the file stream download negotiation process. The ++ * appropriate stream negotiator's initiate incoming stream is called after ++ * an appropriate file transfer method is selected. The manager will respond ++ * to the initiator with the selected means of transfer, then it will handle ++ * any negotiation specific to the particular transfer method. This method ++ * returns the InputStream, ready to transfer the file. ++ * ++ * @param initiation The initiation that triggered this download. ++ * @return After the negotiation process is complete, the InputStream to ++ * write a file to is returned. ++ * @throws XMPPException If an error occurs during this process an XMPPException is ++ * thrown. ++ * @throws InterruptedException If thread is interrupted. ++ */ ++ public abstract InputStream createIncomingStream(StreamInitiation initiation) ++ throws XMPPException, InterruptedException; ++ ++ /** ++ * This method handles the file upload stream negotiation process. The ++ * particular stream negotiator is determined during the file transfer ++ * negotiation process. This method returns the OutputStream to transmit the ++ * file to the remote user. ++ * ++ * @param streamID The streamID that uniquely identifies the file transfer. ++ * @param initiator The fully-qualified JID of the initiator of the file transfer. ++ * @param target The fully-qualified JID of the target or receiver of the file ++ * transfer. ++ * @return The negotiated stream ready for data. ++ * @throws XMPPException If an error occurs during the negotiation process an ++ * exception will be thrown. ++ */ ++ public abstract OutputStream createOutgoingStream(String streamID, ++ String initiator, String target) throws XMPPException; ++ ++ /** ++ * Returns the XMPP namespace reserved for this particular type of file ++ * transfer. ++ * ++ * @return Returns the XMPP namespace reserved for this particular type of ++ * file transfer. ++ */ ++ public abstract String[] getNamespaces(); ++ ++ /** ++ * Cleanup any and all resources associated with this negotiator. ++ */ ++ public abstract void cleanup(); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/Affiliate.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/Affiliate.java +new file mode 100644 +index 0000000..09a04f6 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/Affiliate.java +@@ -0,0 +1,98 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.muc; ++ ++import org.jivesoftware.smackx.packet.MUCAdmin; ++import org.jivesoftware.smackx.packet.MUCOwner; ++ ++/** ++ * Represents an affiliation of a user to a given room. The affiliate's information will always have ++ * the bare jid of the real user and its affiliation. If the affiliate is an occupant of the room ++ * then we will also have information about the role and nickname of the user in the room. ++ * ++ * @author Gaston Dombiak ++ */ ++public class Affiliate { ++ // Fields that must have a value ++ private String jid; ++ private String affiliation; ++ ++ // Fields that may have a value ++ private String role; ++ private String nick; ++ ++ Affiliate(MUCOwner.Item item) { ++ super(); ++ this.jid = item.getJid(); ++ this.affiliation = item.getAffiliation(); ++ this.role = item.getRole(); ++ this.nick = item.getNick(); ++ } ++ ++ Affiliate(MUCAdmin.Item item) { ++ super(); ++ this.jid = item.getJid(); ++ this.affiliation = item.getAffiliation(); ++ this.role = item.getRole(); ++ this.nick = item.getNick(); ++ } ++ ++ /** ++ * Returns the bare JID of the affiliated user. This information will always be available. ++ * ++ * @return the bare JID of the affiliated user. ++ */ ++ public String getJid() { ++ return jid; ++ } ++ ++ /** ++ * Returns the affiliation of the afffiliated user. Possible affiliations are: "owner", "admin", ++ * "member", "outcast". This information will always be available. ++ * ++ * @return the affiliation of the afffiliated user. ++ */ ++ public String getAffiliation() { ++ return affiliation; ++ } ++ ++ /** ++ * Returns the current role of the affiliated user if the user is currently in the room. ++ * If the user is not present in the room then the answer will be null. ++ * ++ * @return the current role of the affiliated user in the room or null if the user is not in ++ * the room. ++ */ ++ public String getRole() { ++ return role; ++ } ++ ++ /** ++ * Returns the current nickname of the affiliated user if the user is currently in the room. ++ * If the user is not present in the room then the answer will be null. ++ * ++ * @return the current nickname of the affiliated user in the room or null if the user is not in ++ * the room. ++ */ ++ public String getNick() { ++ return nick; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/ConnectionDetachedPacketCollector.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/ConnectionDetachedPacketCollector.java +new file mode 100644 +index 0000000..2a719f6 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/ConnectionDetachedPacketCollector.java +@@ -0,0 +1,145 @@ ++/** ++ * $RCSfile$ ++ * $Revision: 2779 $ ++ * $Date: 2005-09-05 17:00:45 -0300 (Mon, 05 Sep 2005) $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx.muc; ++ ++import org.jivesoftware.smack.SmackConfiguration; ++import org.jivesoftware.smack.packet.Packet; ++ ++import java.util.LinkedList; ++ ++/** ++ * A variant of the {@link org.jivesoftware.smack.PacketCollector} class ++ * that does not force attachment to a <code>Connection</code> ++ * on creation and no filter is required. Used to collect message ++ * packets targeted to a group chat room. ++ * ++ * @author Larry Kirschner ++ */ ++class ConnectionDetachedPacketCollector { ++ /** ++ * Max number of packets that any one collector can hold. After the max is ++ * reached, older packets will be automatically dropped from the queue as ++ * new packets are added. ++ */ ++ private int maxPackets = SmackConfiguration.getPacketCollectorSize(); ++ ++ private LinkedList<Packet> resultQueue; ++ ++ /** ++ * Creates a new packet collector. If the packet filter is <tt>null</tt>, then ++ * all packets will match this collector. ++ */ ++ public ConnectionDetachedPacketCollector() { ++ this.resultQueue = new LinkedList<Packet>(); ++ } ++ ++ /** ++ * Creates a new packet collector. If the packet filter is <tt>null</tt>, then ++ * all packets will match this collector. ++ */ ++ public ConnectionDetachedPacketCollector(int maxSize) { ++ this.resultQueue = new LinkedList<Packet>(); ++ maxPackets = maxSize; ++ } ++ ++ /** ++ * Polls to see if a packet is currently available and returns it, or ++ * immediately returns <tt>null</tt> if no packets are currently in the ++ * result queue. ++ * ++ * @return the next packet result, or <tt>null</tt> if there are no more ++ * results. ++ */ ++ public synchronized Packet pollResult() { ++ if (resultQueue.isEmpty()) { ++ return null; ++ } ++ else { ++ return resultQueue.removeLast(); ++ } ++ } ++ ++ /** ++ * Returns the next available packet. The method call will block (not return) ++ * until a packet is available. ++ * ++ * @return the next available packet. ++ */ ++ public synchronized Packet nextResult() { ++ // Wait indefinitely until there is a result to return. ++ while (resultQueue.isEmpty()) { ++ try { ++ wait(); ++ } ++ catch (InterruptedException ie) { ++ // Ignore. ++ } ++ } ++ return resultQueue.removeLast(); ++ } ++ ++ /** ++ * Returns the next available packet. The method call will block (not return) ++ * until a packet is available or the <tt>timeout</tt> has elapased. If the ++ * timeout elapses without a result, <tt>null</tt> will be returned. ++ * ++ * @param timeout the amount of time to wait for the next packet (in milleseconds). ++ * @return the next available packet. ++ */ ++ public synchronized Packet nextResult(long timeout) { ++ // Wait up to the specified amount of time for a result. ++ if (resultQueue.isEmpty()) { ++ try { ++ wait(timeout); ++ } ++ catch (InterruptedException ie) { ++ // Ignore. ++ } ++ } ++ // If still no result, return null. ++ if (resultQueue.isEmpty()) { ++ return null; ++ } ++ else { ++ return resultQueue.removeLast(); ++ } ++ } ++ ++ /** ++ * Processes a packet to see if it meets the criteria for this packet collector. ++ * If so, the packet is added to the result queue. ++ * ++ * @param packet the packet to process. ++ */ ++ protected synchronized void processPacket(Packet packet) { ++ if (packet == null) { ++ return; ++ } ++ // If the max number of packets has been reached, remove the oldest one. ++ if (resultQueue.size() == maxPackets) { ++ resultQueue.removeLast(); ++ } ++ // Add the new packet. ++ resultQueue.addFirst(packet); ++ // Notify waiting threads a result is available. ++ notifyAll(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DeafOccupantInterceptor.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DeafOccupantInterceptor.java +new file mode 100644 +index 0000000..c0351c9 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DeafOccupantInterceptor.java +@@ -0,0 +1,76 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx.muc; ++ ++import org.jivesoftware.smack.PacketInterceptor; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.packet.Presence; ++ ++/** ++ * Packet interceptor that will intercept presence packets sent to the MUC service to indicate ++ * that the user wants to be a deaf occupant. A user can only indicate that he wants to be a ++ * deaf occupant while joining the room. It is not possible to become deaf or stop being deaf ++ * after the user joined the room.<p> ++ * ++ * Deaf occupants will not get messages broadcasted to all room occupants. However, they will ++ * be able to get private messages, presences, IQ packets or room history. To use this ++ * functionality you will need to send the message ++ * {@link MultiUserChat#addPresenceInterceptor(org.jivesoftware.smack.PacketInterceptor)} and ++ * pass this interceptor as the parameter.<p> ++ * ++ * Note that this is a custom extension to the MUC service so it may not work with other servers ++ * than Wildfire. ++ * ++ * @author Gaston Dombiak ++ */ ++public class DeafOccupantInterceptor implements PacketInterceptor { ++ ++ public void interceptPacket(Packet packet) { ++ Presence presence = (Presence) packet; ++ // Check if user is joining a room ++ if (Presence.Type.available == presence.getType() && ++ presence.getExtension("x", "http://jabber.org/protocol/muc") != null) { ++ // Add extension that indicates that user wants to be a deaf occupant ++ packet.addExtension(new DeafExtension()); ++ } ++ } ++ ++ private static class DeafExtension implements PacketExtension { ++ ++ public String getElementName() { ++ return "x"; ++ } ++ ++ public String getNamespace() { ++ return "http://jivesoftware.org/protocol/muc"; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()) ++ .append("\">"); ++ buf.append("<deaf-occupant/>"); ++ buf.append("</").append(getElementName()).append(">"); ++ return buf.toString(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DefaultParticipantStatusListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DefaultParticipantStatusListener.java +new file mode 100644 +index 0000000..6eb9efa +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DefaultParticipantStatusListener.java +@@ -0,0 +1,79 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.muc; ++ ++/** ++ * Default implementation of the ParticipantStatusListener interface.<p> ++ * ++ * This class does not provide any behavior by default. It just avoids having ++ * to implement all the inteface methods if the user is only interested in implementing ++ * some of the methods. ++ * ++ * @author Gaston Dombiak ++ */ ++public class DefaultParticipantStatusListener implements ParticipantStatusListener { ++ ++ public void joined(String participant) { ++ } ++ ++ public void left(String participant) { ++ } ++ ++ public void kicked(String participant, String actor, String reason) { ++ } ++ ++ public void voiceGranted(String participant) { ++ } ++ ++ public void voiceRevoked(String participant) { ++ } ++ ++ public void banned(String participant, String actor, String reason) { ++ } ++ ++ public void membershipGranted(String participant) { ++ } ++ ++ public void membershipRevoked(String participant) { ++ } ++ ++ public void moderatorGranted(String participant) { ++ } ++ ++ public void moderatorRevoked(String participant) { ++ } ++ ++ public void ownershipGranted(String participant) { ++ } ++ ++ public void ownershipRevoked(String participant) { ++ } ++ ++ public void adminGranted(String participant) { ++ } ++ ++ public void adminRevoked(String participant) { ++ } ++ ++ public void nicknameChanged(String participant, String newNickname) { ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DefaultUserStatusListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DefaultUserStatusListener.java +new file mode 100644 +index 0000000..de7cc87 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DefaultUserStatusListener.java +@@ -0,0 +1,70 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.muc; ++ ++/** ++ * Default implementation of the UserStatusListener interface.<p> ++ * ++ * This class does not provide any behavior by default. It just avoids having ++ * to implement all the inteface methods if the user is only interested in implementing ++ * some of the methods. ++ * ++ * @author Gaston Dombiak ++ */ ++public class DefaultUserStatusListener implements UserStatusListener { ++ ++ public void kicked(String actor, String reason) { ++ } ++ ++ public void voiceGranted() { ++ } ++ ++ public void voiceRevoked() { ++ } ++ ++ public void banned(String actor, String reason) { ++ } ++ ++ public void membershipGranted() { ++ } ++ ++ public void membershipRevoked() { ++ } ++ ++ public void moderatorGranted() { ++ } ++ ++ public void moderatorRevoked() { ++ } ++ ++ public void ownershipGranted() { ++ } ++ ++ public void ownershipRevoked() { ++ } ++ ++ public void adminGranted() { ++ } ++ ++ public void adminRevoked() { ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DiscussionHistory.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DiscussionHistory.java +new file mode 100644 +index 0000000..036f6cb +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DiscussionHistory.java +@@ -0,0 +1,173 @@ ++/** ++ * $RCSfile$ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.muc; ++ ++import java.util.Date; ++ ++import org.jivesoftware.smackx.packet.MUCInitialPresence; ++ ++/** ++ * The DiscussionHistory class controls the number of characters or messages to receive ++ * when entering a room. The room will decide the amount of history to return if you don't ++ * specify a DiscussionHistory while joining a room.<p> ++ * ++ * You can use some or all of these variable to control the amount of history to receive: ++ * <ul> ++ * <li>maxchars -> total number of characters to receive in the history. ++ * <li>maxstanzas -> total number of messages to receive in the history. ++ * <li>seconds -> only the messages received in the last "X" seconds will be included in the ++ * history. ++ * <li>since -> only the messages received since the datetime specified will be included in ++ * the history. ++ * </ul> ++ * ++ * Note: Setting maxchars to 0 indicates that the user requests to receive no history. ++ * ++ * @author Gaston Dombiak ++ */ ++public class DiscussionHistory { ++ ++ private int maxChars = -1; ++ private int maxStanzas = -1; ++ private int seconds = -1; ++ private Date since; ++ ++ /** ++ * Returns the total number of characters to receive in the history. ++ * ++ * @return total number of characters to receive in the history. ++ */ ++ public int getMaxChars() { ++ return maxChars; ++ } ++ ++ /** ++ * Returns the total number of messages to receive in the history. ++ * ++ * @return the total number of messages to receive in the history. ++ */ ++ public int getMaxStanzas() { ++ return maxStanzas; ++ } ++ ++ /** ++ * Returns the number of seconds to use to filter the messages received during that time. ++ * In other words, only the messages received in the last "X" seconds will be included in ++ * the history. ++ * ++ * @return the number of seconds to use to filter the messages received during that time. ++ */ ++ public int getSeconds() { ++ return seconds; ++ } ++ ++ /** ++ * Returns the since date to use to filter the messages received during that time. ++ * In other words, only the messages received since the datetime specified will be ++ * included in the history. ++ * ++ * @return the since date to use to filter the messages received during that time. ++ */ ++ public Date getSince() { ++ return since; ++ } ++ ++ /** ++ * Sets the total number of characters to receive in the history. ++ * ++ * @param maxChars the total number of characters to receive in the history. ++ */ ++ public void setMaxChars(int maxChars) { ++ this.maxChars = maxChars; ++ } ++ ++ /** ++ * Sets the total number of messages to receive in the history. ++ * ++ * @param maxStanzas the total number of messages to receive in the history. ++ */ ++ public void setMaxStanzas(int maxStanzas) { ++ this.maxStanzas = maxStanzas; ++ } ++ ++ /** ++ * Sets the number of seconds to use to filter the messages received during that time. ++ * In other words, only the messages received in the last "X" seconds will be included in ++ * the history. ++ * ++ * @param seconds the number of seconds to use to filter the messages received during ++ * that time. ++ */ ++ public void setSeconds(int seconds) { ++ this.seconds = seconds; ++ } ++ ++ /** ++ * Sets the since date to use to filter the messages received during that time. ++ * In other words, only the messages received since the datetime specified will be ++ * included in the history. ++ * ++ * @param since the since date to use to filter the messages received during that time. ++ */ ++ public void setSince(Date since) { ++ this.since = since; ++ } ++ ++ /** ++ * Returns true if the history has been configured with some values. ++ * ++ * @return true if the history has been configured with some values. ++ */ ++ private boolean isConfigured() { ++ return maxChars > -1 || maxStanzas > -1 || seconds > -1 || since != null; ++ } ++ ++ /** ++ * Returns the History that manages the amount of discussion history provided on entering a ++ * room. ++ * ++ * @return the History that manages the amount of discussion history provided on entering a ++ * room. ++ */ ++ MUCInitialPresence.History getMUCHistory() { ++ // Return null if the history was not properly configured ++ if (!isConfigured()) { ++ return null; ++ } ++ ++ MUCInitialPresence.History mucHistory = new MUCInitialPresence.History(); ++ if (maxChars > -1) { ++ mucHistory.setMaxChars(maxChars); ++ } ++ if (maxStanzas > -1) { ++ mucHistory.setMaxStanzas(maxStanzas); ++ } ++ if (seconds > -1) { ++ mucHistory.setSeconds(seconds); ++ } ++ if (since != null) { ++ mucHistory.setSince(since); ++ } ++ return mucHistory; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/HostedRoom.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/HostedRoom.java +new file mode 100644 +index 0000000..7cd580b +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/HostedRoom.java +@@ -0,0 +1,65 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.muc; ++ ++import org.jivesoftware.smackx.packet.DiscoverItems; ++ ++/** ++ * Hosted rooms by a chat service may be discovered if they are configured to appear in the room ++ * directory . The information that may be discovered is the XMPP address of the room and the room ++ * name. The address of the room may be used for obtaining more detailed information ++ * {@link org.jivesoftware.smackx.muc.MultiUserChat#getRoomInfo(org.jivesoftware.smack.Connection, String)} ++ * or could be used for joining the room ++ * {@link org.jivesoftware.smackx.muc.MultiUserChat#MultiUserChat(org.jivesoftware.smack.Connection, String)} ++ * and {@link org.jivesoftware.smackx.muc.MultiUserChat#join(String)}. ++ * ++ * @author Gaston Dombiak ++ */ ++public class HostedRoom { ++ ++ private String jid; ++ ++ private String name; ++ ++ public HostedRoom(DiscoverItems.Item item) { ++ super(); ++ jid = item.getEntityID(); ++ name = item.getName(); ++ } ++ ++ /** ++ * Returns the XMPP address of the hosted room by the chat service. This address may be used ++ * when creating a <code>MultiUserChat</code> when joining a room. ++ * ++ * @return the XMPP address of the hosted room by the chat service. ++ */ ++ public String getJid() { ++ return jid; ++ } ++ ++ /** ++ * Returns the name of the room. ++ * ++ * @return the name of the room. ++ */ ++ public String getName() { ++ return name; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/InvitationListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/InvitationListener.java +new file mode 100644 +index 0000000..34c915d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/InvitationListener.java +@@ -0,0 +1,49 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.muc; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.packet.Message; ++ ++/** ++ * A listener that is fired anytime an invitation to join a MUC room is received. ++ * ++ * @author Gaston Dombiak ++ */ ++public interface InvitationListener { ++ ++ /** ++ * Called when the an invitation to join a MUC room is received.<p> ++ * ++ * If the room is password-protected, the invitee will receive a password to use to join ++ * the room. If the room is members-only, the the invitee may be added to the member list. ++ * ++ * @param conn the Connection that received the invitation. ++ * @param room the room that invitation refers to. ++ * @param inviter the inviter that sent the invitation. (e.g. crone1@shakespeare.lit). ++ * @param reason the reason why the inviter sent the invitation. ++ * @param password the password to use when joining the room. ++ * @param message the message used by the inviter to send the invitation. ++ */ ++ public abstract void invitationReceived(Connection conn, String room, String inviter, String reason, ++ String password, Message message); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/InvitationRejectionListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/InvitationRejectionListener.java +new file mode 100644 +index 0000000..1580c6f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/InvitationRejectionListener.java +@@ -0,0 +1,38 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.muc; ++ ++/** ++ * A listener that is fired anytime an invitee declines or rejects an invitation. ++ * ++ * @author Gaston Dombiak ++ */ ++public interface InvitationRejectionListener { ++ ++ /** ++ * Called when the invitee declines the invitation. ++ * ++ * @param invitee the invitee that declined the invitation. (e.g. hecate@shakespeare.lit). ++ * @param reason the reason why the invitee declined the invitation. ++ */ ++ public abstract void invitationDeclined(String invitee, String reason); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/MultiUserChat.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/MultiUserChat.java +new file mode 100644 +index 0000000..aee90ea +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/MultiUserChat.java +@@ -0,0 +1,2717 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.muc; ++ ++import java.lang.ref.WeakReference; ++import java.lang.reflect.InvocationTargetException; ++import java.lang.reflect.Method; ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import java.util.WeakHashMap; ++import java.util.concurrent.ConcurrentHashMap; ++ ++import org.jivesoftware.smack.Chat; ++import org.jivesoftware.smack.ConnectionCreationListener; ++import org.jivesoftware.smack.ConnectionListener; ++import org.jivesoftware.smack.MessageListener; ++import org.jivesoftware.smack.PacketCollector; ++import org.jivesoftware.smack.PacketInterceptor; ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.SmackConfiguration; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.AndFilter; ++import org.jivesoftware.smack.filter.FromMatchesFilter; ++import org.jivesoftware.smack.filter.MessageTypeFilter; ++import org.jivesoftware.smack.filter.PacketExtensionFilter; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Message; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.Presence; ++import org.jivesoftware.smack.packet.Registration; ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.NodeInformationProvider; ++import org.jivesoftware.smackx.ServiceDiscoveryManager; ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++import org.jivesoftware.smackx.packet.DiscoverItems; ++import org.jivesoftware.smackx.packet.MUCAdmin; ++import org.jivesoftware.smackx.packet.MUCInitialPresence; ++import org.jivesoftware.smackx.packet.MUCOwner; ++import org.jivesoftware.smackx.packet.MUCUser; ++ ++/** ++ * A MultiUserChat is a conversation that takes place among many users in a virtual ++ * room. A room could have many occupants with different affiliation and roles. ++ * Possible affiliatons are "owner", "admin", "member", and "outcast". Possible roles ++ * are "moderator", "participant", and "visitor". Each role and affiliation guarantees ++ * different privileges (e.g. Send messages to all occupants, Kick participants and visitors, ++ * Grant voice, Edit member list, etc.). ++ * ++ * @author Gaston Dombiak, Larry Kirschner ++ */ ++public class MultiUserChat { ++ ++ private final static String discoNamespace = "http://jabber.org/protocol/muc"; ++ private final static String discoNode = "http://jabber.org/protocol/muc#rooms"; ++ ++ private static Map<Connection, List<String>> joinedRooms = ++ new WeakHashMap<Connection, List<String>>(); ++ ++ private Connection connection; ++ private String room; ++ private String subject; ++ private String nickname = null; ++ private boolean joined = false; ++ private Map<String, Presence> occupantsMap = new ConcurrentHashMap<String, Presence>(); ++ ++ private final List<InvitationRejectionListener> invitationRejectionListeners = ++ new ArrayList<InvitationRejectionListener>(); ++ private final List<SubjectUpdatedListener> subjectUpdatedListeners = ++ new ArrayList<SubjectUpdatedListener>(); ++ private final List<UserStatusListener> userStatusListeners = ++ new ArrayList<UserStatusListener>(); ++ private final List<ParticipantStatusListener> participantStatusListeners = ++ new ArrayList<ParticipantStatusListener>(); ++ ++ private PacketFilter presenceFilter; ++ private List<PacketInterceptor> presenceInterceptors = new ArrayList<PacketInterceptor>(); ++ private PacketFilter messageFilter; ++ private RoomListenerMultiplexor roomListenerMultiplexor; ++ private ConnectionDetachedPacketCollector messageCollector; ++ private List<PacketListener> connectionListeners = new ArrayList<PacketListener>(); ++ ++ static { ++ Connection.addConnectionCreationListener(new ConnectionCreationListener() { ++ public void connectionCreated(final Connection connection) { ++ // Set on every established connection that this client supports the Multi-User ++ // Chat protocol. This information will be used when another client tries to ++ // discover whether this client supports MUC or not. ++ ServiceDiscoveryManager.getInstanceFor(connection).addFeature(discoNamespace); ++ // Set the NodeInformationProvider that will provide information about the ++ // joined rooms whenever a disco request is received ++ ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider( ++ discoNode, ++ new NodeInformationProvider() { ++ public List<DiscoverItems.Item> getNodeItems() { ++ List<DiscoverItems.Item> answer = new ArrayList<DiscoverItems.Item>(); ++ Iterator<String> rooms=MultiUserChat.getJoinedRooms(connection); ++ while (rooms.hasNext()) { ++ answer.add(new DiscoverItems.Item(rooms.next())); ++ } ++ return answer; ++ } ++ ++ public List<String> getNodeFeatures() { ++ return null; ++ } ++ ++ public List<DiscoverInfo.Identity> getNodeIdentities() { ++ return null; ++ } ++ }); ++ } ++ }); ++ } ++ ++ /** ++ * Creates a new multi user chat with the specified connection and room name. Note: no ++ * information is sent to or received from the server until you attempt to ++ * {@link #join(String) join} the chat room. On some server implementations, ++ * the room will not be created until the first person joins it.<p> ++ * ++ * Most XMPP servers use a sub-domain for the chat service (eg chat.example.com ++ * for the XMPP server example.com). You must ensure that the room address you're ++ * trying to connect to includes the proper chat sub-domain. ++ * ++ * @param connection the XMPP connection. ++ * @param room the name of the room in the form "roomName@service", where ++ * "service" is the hostname at which the multi-user chat ++ * service is running. Make sure to provide a valid JID. ++ */ ++ public MultiUserChat(Connection connection, String room) { ++ this.connection = connection; ++ this.room = room.toLowerCase(); ++ init(); ++ } ++ ++ /** ++ * Returns true if the specified user supports the Multi-User Chat protocol. ++ * ++ * @param connection the connection to use to perform the service discovery. ++ * @param user the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com. ++ * @return a boolean indicating whether the specified user supports the MUC protocol. ++ */ ++ public static boolean isServiceEnabled(Connection connection, String user) { ++ try { ++ DiscoverInfo result = ++ ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(user); ++ return result.containsFeature(discoNamespace); ++ } ++ catch (XMPPException e) { ++ e.printStackTrace(); ++ return false; ++ } ++ } ++ ++ /** ++ * Returns an Iterator on the rooms where the user has joined using a given connection. ++ * The Iterator will contain Strings where each String represents a room ++ * (e.g. room@muc.jabber.org). ++ * ++ * @param connection the connection used to join the rooms. ++ * @return an Iterator on the rooms where the user has joined using a given connection. ++ */ ++ private static Iterator<String> getJoinedRooms(Connection connection) { ++ List<String> rooms = joinedRooms.get(connection); ++ if (rooms != null) { ++ return rooms.iterator(); ++ } ++ // Return an iterator on an empty collection (i.e. the user never joined a room) ++ return new ArrayList<String>().iterator(); ++ } ++ ++ /** ++ * Returns an Iterator on the rooms where the requested user has joined. The Iterator will ++ * contain Strings where each String represents a room (e.g. room@muc.jabber.org). ++ * ++ * @param connection the connection to use to perform the service discovery. ++ * @param user the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com. ++ * @return an Iterator on the rooms where the requested user has joined. ++ */ ++ public static Iterator<String> getJoinedRooms(Connection connection, String user) { ++ try { ++ ArrayList<String> answer = new ArrayList<String>(); ++ // Send the disco packet to the user ++ DiscoverItems result = ++ ServiceDiscoveryManager.getInstanceFor(connection).discoverItems(user, discoNode); ++ // Collect the entityID for each returned item ++ for (Iterator<DiscoverItems.Item> items=result.getItems(); items.hasNext();) { ++ answer.add(items.next().getEntityID()); ++ } ++ return answer.iterator(); ++ } ++ catch (XMPPException e) { ++ e.printStackTrace(); ++ // Return an iterator on an empty collection ++ return new ArrayList<String>().iterator(); ++ } ++ } ++ ++ /** ++ * Returns the discovered information of a given room without actually having to join the room. ++ * The server will provide information only for rooms that are public. ++ * ++ * @param connection the XMPP connection to use for discovering information about the room. ++ * @param room the name of the room in the form "roomName@service" of which we want to discover ++ * its information. ++ * @return the discovered information of a given room without actually having to join the room. ++ * @throws XMPPException if an error occured while trying to discover information of a room. ++ */ ++ public static RoomInfo getRoomInfo(Connection connection, String room) ++ throws XMPPException { ++ DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(room); ++ return new RoomInfo(info); ++ } ++ ++ /** ++ * Returns a collection with the XMPP addresses of the Multi-User Chat services. ++ * ++ * @param connection the XMPP connection to use for discovering Multi-User Chat services. ++ * @return a collection with the XMPP addresses of the Multi-User Chat services. ++ * @throws XMPPException if an error occured while trying to discover MUC services. ++ */ ++ public static Collection<String> getServiceNames(Connection connection) throws XMPPException { ++ final List<String> answer = new ArrayList<String>(); ++ ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); ++ DiscoverItems items = discoManager.discoverItems(connection.getServiceName()); ++ for (Iterator<DiscoverItems.Item> it = items.getItems(); it.hasNext();) { ++ DiscoverItems.Item item = it.next(); ++ try { ++ DiscoverInfo info = discoManager.discoverInfo(item.getEntityID()); ++ if (info.containsFeature("http://jabber.org/protocol/muc")) { ++ answer.add(item.getEntityID()); ++ } ++ } ++ catch (XMPPException e) { ++ // Trouble finding info in some cases. This is a workaround for ++ // discovering info on remote servers. ++ } ++ } ++ return answer; ++ } ++ ++ /** ++ * Returns a collection of HostedRooms where each HostedRoom has the XMPP address of the room ++ * and the room's name. Once discovered the rooms hosted by a chat service it is possible to ++ * discover more detailed room information or join the room. ++ * ++ * @param connection the XMPP connection to use for discovering hosted rooms by the MUC service. ++ * @param serviceName the service that is hosting the rooms to discover. ++ * @return a collection of HostedRooms. ++ * @throws XMPPException if an error occured while trying to discover the information. ++ */ ++ public static Collection<HostedRoom> getHostedRooms(Connection connection, String serviceName) ++ throws XMPPException { ++ List<HostedRoom> answer = new ArrayList<HostedRoom>(); ++ ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); ++ DiscoverItems items = discoManager.discoverItems(serviceName); ++ for (Iterator<DiscoverItems.Item> it = items.getItems(); it.hasNext();) { ++ answer.add(new HostedRoom(it.next())); ++ } ++ return answer; ++ } ++ ++ /** ++ * Returns the name of the room this MultiUserChat object represents. ++ * ++ * @return the multi user chat room name. ++ */ ++ public String getRoom() { ++ return room; ++ } ++ ++ /** ++ * Creates the room according to some default configuration, assign the requesting user ++ * as the room owner, and add the owner to the room but not allow anyone else to enter ++ * the room (effectively "locking" the room). The requesting user will join the room ++ * under the specified nickname as soon as the room has been created.<p> ++ * ++ * To create an "Instant Room", that means a room with some default configuration that is ++ * available for immediate access, the room's owner should send an empty form after creating ++ * the room. {@link #sendConfigurationForm(Form)}<p> ++ * ++ * To create a "Reserved Room", that means a room manually configured by the room creator ++ * before anyone is allowed to enter, the room's owner should complete and send a form after ++ * creating the room. Once the completed configutation form is sent to the server, the server ++ * will unlock the room. {@link #sendConfigurationForm(Form)} ++ * ++ * @param nickname the nickname to use. ++ * @throws XMPPException if the room couldn't be created for some reason ++ * (e.g. room already exists; user already joined to an existant room or ++ * 405 error if the user is not allowed to create the room) ++ */ ++ public synchronized void create(String nickname) throws XMPPException { ++ if (nickname == null || nickname.equals("")) { ++ throw new IllegalArgumentException("Nickname must not be null or blank."); ++ } ++ // If we've already joined the room, leave it before joining under a new ++ // nickname. ++ if (joined) { ++ throw new IllegalStateException("Creation failed - User already joined the room."); ++ } ++ // We create a room by sending a presence packet to room@service/nick ++ // and signal support for MUC. The owner will be automatically logged into the room. ++ Presence joinPresence = new Presence(Presence.Type.available); ++ joinPresence.setTo(room + "/" + nickname); ++ // Indicate the the client supports MUC ++ joinPresence.addExtension(new MUCInitialPresence()); ++ // Invoke presence interceptors so that extra information can be dynamically added ++ for (PacketInterceptor packetInterceptor : presenceInterceptors) { ++ packetInterceptor.interceptPacket(joinPresence); ++ } ++ ++ // Wait for a presence packet back from the server. ++ PacketFilter responseFilter = ++ new AndFilter( ++ new FromMatchesFilter(room + "/" + nickname), ++ new PacketTypeFilter(Presence.class)); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Send create & join packet. ++ connection.sendPacket(joinPresence); ++ // Wait up to a certain number of seconds for a reply. ++ Presence presence = ++ (Presence) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (presence == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (presence.getError() != null) { ++ throw new XMPPException(presence.getError()); ++ } ++ // Whether the room existed before or was created, the user has joined the room ++ this.nickname = nickname; ++ joined = true; ++ userHasJoined(); ++ ++ // Look for confirmation of room creation from the server ++ MUCUser mucUser = getMUCUserExtension(presence); ++ if (mucUser != null && mucUser.getStatus() != null) { ++ if ("201".equals(mucUser.getStatus().getCode())) { ++ // Room was created and the user has joined the room ++ return; ++ } ++ } ++ // We need to leave the room since it seems that the room already existed ++ leave(); ++ throw new XMPPException("Creation failed - Missing acknowledge of room creation."); ++ } ++ ++ /** ++ * Joins the chat room using the specified nickname. If already joined ++ * using another nickname, this method will first leave the room and then ++ * re-join using the new nickname. The default timeout of Smack for a reply ++ * from the group chat server that the join succeeded will be used. After ++ * joining the room, the room will decide the amount of history to send. ++ * ++ * @param nickname the nickname to use. ++ * @throws XMPPException if an error occurs joining the room. In particular, a ++ * 401 error can occur if no password was provided and one is required; or a ++ * 403 error can occur if the user is banned; or a ++ * 404 error can occur if the room does not exist or is locked; or a ++ * 407 error can occur if user is not on the member list; or a ++ * 409 error can occur if someone is already in the group chat with the same nickname. ++ */ ++ public void join(String nickname) throws XMPPException { ++ join(nickname, null, null, SmackConfiguration.getPacketReplyTimeout()); ++ } ++ ++ /** ++ * Joins the chat room using the specified nickname and password. If already joined ++ * using another nickname, this method will first leave the room and then ++ * re-join using the new nickname. The default timeout of Smack for a reply ++ * from the group chat server that the join succeeded will be used. After ++ * joining the room, the room will decide the amount of history to send.<p> ++ * ++ * A password is required when joining password protected rooms. If the room does ++ * not require a password there is no need to provide one. ++ * ++ * @param nickname the nickname to use. ++ * @param password the password to use. ++ * @throws XMPPException if an error occurs joining the room. In particular, a ++ * 401 error can occur if no password was provided and one is required; or a ++ * 403 error can occur if the user is banned; or a ++ * 404 error can occur if the room does not exist or is locked; or a ++ * 407 error can occur if user is not on the member list; or a ++ * 409 error can occur if someone is already in the group chat with the same nickname. ++ */ ++ public void join(String nickname, String password) throws XMPPException { ++ join(nickname, password, null, SmackConfiguration.getPacketReplyTimeout()); ++ } ++ ++ /** ++ * Joins the chat room using the specified nickname and password. If already joined ++ * using another nickname, this method will first leave the room and then ++ * re-join using the new nickname.<p> ++ * ++ * To control the amount of history to receive while joining a room you will need to provide ++ * a configured DiscussionHistory object.<p> ++ * ++ * A password is required when joining password protected rooms. If the room does ++ * not require a password there is no need to provide one.<p> ++ * ++ * If the room does not already exist when the user seeks to enter it, the server will ++ * decide to create a new room or not. ++ * ++ * @param nickname the nickname to use. ++ * @param password the password to use. ++ * @param history the amount of discussion history to receive while joining a room. ++ * @param timeout the amount of time to wait for a reply from the MUC service(in milleseconds). ++ * @throws XMPPException if an error occurs joining the room. In particular, a ++ * 401 error can occur if no password was provided and one is required; or a ++ * 403 error can occur if the user is banned; or a ++ * 404 error can occur if the room does not exist or is locked; or a ++ * 407 error can occur if user is not on the member list; or a ++ * 409 error can occur if someone is already in the group chat with the same nickname. ++ */ ++ public synchronized void join( ++ String nickname, ++ String password, ++ DiscussionHistory history, ++ long timeout) ++ throws XMPPException { ++ if (nickname == null || nickname.equals("")) { ++ throw new IllegalArgumentException("Nickname must not be null or blank."); ++ } ++ // If we've already joined the room, leave it before joining under a new ++ // nickname. ++ if (joined) { ++ leave(); ++ } ++ // We join a room by sending a presence packet where the "to" ++ // field is in the form "roomName@service/nickname" ++ Presence joinPresence = new Presence(Presence.Type.available); ++ joinPresence.setTo(room + "/" + nickname); ++ ++ // Indicate the the client supports MUC ++ MUCInitialPresence mucInitialPresence = new MUCInitialPresence(); ++ if (password != null) { ++ mucInitialPresence.setPassword(password); ++ } ++ if (history != null) { ++ mucInitialPresence.setHistory(history.getMUCHistory()); ++ } ++ joinPresence.addExtension(mucInitialPresence); ++ // Invoke presence interceptors so that extra information can be dynamically added ++ for (PacketInterceptor packetInterceptor : presenceInterceptors) { ++ packetInterceptor.interceptPacket(joinPresence); ++ } ++ ++ // Wait for a presence packet back from the server. ++ PacketFilter responseFilter = ++ new AndFilter( ++ new FromMatchesFilter(room + "/" + nickname), ++ new PacketTypeFilter(Presence.class)); ++ PacketCollector response = null; ++ Presence presence; ++ try { ++ response = connection.createPacketCollector(responseFilter); ++ // Send join packet. ++ connection.sendPacket(joinPresence); ++ // Wait up to a certain number of seconds for a reply. ++ presence = (Presence) response.nextResult(timeout); ++ } ++ finally { ++ // Stop queuing results ++ if (response != null) { ++ response.cancel(); ++ } ++ } ++ ++ if (presence == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (presence.getError() != null) { ++ throw new XMPPException(presence.getError()); ++ } ++ this.nickname = nickname; ++ joined = true; ++ userHasJoined(); ++ } ++ ++ /** ++ * Returns true if currently in the multi user chat (after calling the {@link ++ * #join(String)} method). ++ * ++ * @return true if currently in the multi user chat room. ++ */ ++ public boolean isJoined() { ++ return joined; ++ } ++ ++ /** ++ * Leave the chat room. ++ */ ++ public synchronized void leave() { ++ // If not joined already, do nothing. ++ if (!joined) { ++ return; ++ } ++ // We leave a room by sending a presence packet where the "to" ++ // field is in the form "roomName@service/nickname" ++ Presence leavePresence = new Presence(Presence.Type.unavailable); ++ leavePresence.setTo(room + "/" + nickname); ++ // Invoke presence interceptors so that extra information can be dynamically added ++ for (PacketInterceptor packetInterceptor : presenceInterceptors) { ++ packetInterceptor.interceptPacket(leavePresence); ++ } ++ connection.sendPacket(leavePresence); ++ // Reset occupant information. ++ occupantsMap.clear(); ++ nickname = null; ++ joined = false; ++ userHasLeft(); ++ } ++ ++ /** ++ * Returns the room's configuration form that the room's owner can use or <tt>null</tt> if ++ * no configuration is possible. The configuration form allows to set the room's language, ++ * enable logging, specify room's type, etc.. ++ * ++ * @return the Form that contains the fields to complete together with the instrucions or ++ * <tt>null</tt> if no configuration is possible. ++ * @throws XMPPException if an error occurs asking the configuration form for the room. ++ */ ++ public Form getConfigurationForm() throws XMPPException { ++ MUCOwner iq = new MUCOwner(); ++ iq.setTo(room); ++ iq.setType(IQ.Type.GET); ++ ++ // Filter packets looking for an answer from the server. ++ PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Request the configuration form to the server. ++ connection.sendPacket(iq); ++ // Wait up to a certain number of seconds for a reply. ++ IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (answer == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (answer.getError() != null) { ++ throw new XMPPException(answer.getError()); ++ } ++ return Form.getFormFrom(answer); ++ } ++ ++ /** ++ * Sends the completed configuration form to the server. The room will be configured ++ * with the new settings defined in the form. If the form is empty then the server ++ * will create an instant room (will use default configuration). ++ * ++ * @param form the form with the new settings. ++ * @throws XMPPException if an error occurs setting the new rooms' configuration. ++ */ ++ public void sendConfigurationForm(Form form) throws XMPPException { ++ MUCOwner iq = new MUCOwner(); ++ iq.setTo(room); ++ iq.setType(IQ.Type.SET); ++ iq.addExtension(form.getDataFormToSend()); ++ ++ // Filter packets looking for an answer from the server. ++ PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Send the completed configuration form to the server. ++ connection.sendPacket(iq); ++ // Wait up to a certain number of seconds for a reply. ++ IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (answer == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (answer.getError() != null) { ++ throw new XMPPException(answer.getError()); ++ } ++ } ++ ++ /** ++ * Returns the room's registration form that an unaffiliated user, can use to become a member ++ * of the room or <tt>null</tt> if no registration is possible. Some rooms may restrict the ++ * privilege to register members and allow only room admins to add new members.<p> ++ * ++ * If the user requesting registration requirements is not allowed to register with the room ++ * (e.g. because that privilege has been restricted), the room will return a "Not Allowed" ++ * error to the user (error code 405). ++ * ++ * @return the registration Form that contains the fields to complete together with the ++ * instrucions or <tt>null</tt> if no registration is possible. ++ * @throws XMPPException if an error occurs asking the registration form for the room or a ++ * 405 error if the user is not allowed to register with the room. ++ */ ++ public Form getRegistrationForm() throws XMPPException { ++ Registration reg = new Registration(); ++ reg.setType(IQ.Type.GET); ++ reg.setTo(room); ++ ++ PacketFilter filter = ++ new AndFilter(new PacketIDFilter(reg.getPacketID()), new PacketTypeFilter(IQ.class)); ++ PacketCollector collector = connection.createPacketCollector(filter); ++ connection.sendPacket(reg); ++ IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ collector.cancel(); ++ if (result == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (result.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(result.getError()); ++ } ++ return Form.getFormFrom(result); ++ } ++ ++ /** ++ * Sends the completed registration form to the server. After the user successfully submits ++ * the form, the room may queue the request for review by the room admins or may immediately ++ * add the user to the member list by changing the user's affiliation from "none" to "member.<p> ++ * ++ * If the desired room nickname is already reserved for that room, the room will return a ++ * "Conflict" error to the user (error code 409). If the room does not support registration, ++ * it will return a "Service Unavailable" error to the user (error code 503). ++ * ++ * @param form the completed registration form. ++ * @throws XMPPException if an error occurs submitting the registration form. In particular, a ++ * 409 error can occur if the desired room nickname is already reserved for that room; ++ * or a 503 error can occur if the room does not support registration. ++ */ ++ public void sendRegistrationForm(Form form) throws XMPPException { ++ Registration reg = new Registration(); ++ reg.setType(IQ.Type.SET); ++ reg.setTo(room); ++ reg.addExtension(form.getDataFormToSend()); ++ ++ PacketFilter filter = ++ new AndFilter(new PacketIDFilter(reg.getPacketID()), new PacketTypeFilter(IQ.class)); ++ PacketCollector collector = connection.createPacketCollector(filter); ++ connection.sendPacket(reg); ++ IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ collector.cancel(); ++ if (result == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (result.getType() == IQ.Type.ERROR) { ++ throw new XMPPException(result.getError()); ++ } ++ } ++ ++ /** ++ * Sends a request to the server to destroy the room. The sender of the request ++ * should be the room's owner. If the sender of the destroy request is not the room's owner ++ * then the server will answer a "Forbidden" error (403). ++ * ++ * @param reason the reason for the room destruction. ++ * @param alternateJID the JID of an alternate location. ++ * @throws XMPPException if an error occurs while trying to destroy the room. ++ * An error can occur which will be wrapped by an XMPPException -- ++ * XMPP error code 403. The error code can be used to present more ++ * appropiate error messages to end-users. ++ */ ++ public void destroy(String reason, String alternateJID) throws XMPPException { ++ MUCOwner iq = new MUCOwner(); ++ iq.setTo(room); ++ iq.setType(IQ.Type.SET); ++ ++ // Create the reason for the room destruction ++ MUCOwner.Destroy destroy = new MUCOwner.Destroy(); ++ destroy.setReason(reason); ++ destroy.setJid(alternateJID); ++ iq.setDestroy(destroy); ++ ++ // Wait for a presence packet back from the server. ++ PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Send the room destruction request. ++ connection.sendPacket(iq); ++ // Wait up to a certain number of seconds for a reply. ++ IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (answer == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (answer.getError() != null) { ++ throw new XMPPException(answer.getError()); ++ } ++ // Reset occupant information. ++ occupantsMap.clear(); ++ nickname = null; ++ joined = false; ++ userHasLeft(); ++ } ++ ++ /** ++ * Invites another user to the room in which one is an occupant. The invitation ++ * will be sent to the room which in turn will forward the invitation to the invitee.<p> ++ * ++ * If the room is password-protected, the invitee will receive a password to use to join ++ * the room. If the room is members-only, the the invitee may be added to the member list. ++ * ++ * @param user the user to invite to the room.(e.g. hecate@shakespeare.lit) ++ * @param reason the reason why the user is being invited. ++ */ ++ public void invite(String user, String reason) { ++ invite(new Message(), user, reason); ++ } ++ ++ /** ++ * Invites another user to the room in which one is an occupant using a given Message. The invitation ++ * will be sent to the room which in turn will forward the invitation to the invitee.<p> ++ * ++ * If the room is password-protected, the invitee will receive a password to use to join ++ * the room. If the room is members-only, the the invitee may be added to the member list. ++ * ++ * @param message the message to use for sending the invitation. ++ * @param user the user to invite to the room.(e.g. hecate@shakespeare.lit) ++ * @param reason the reason why the user is being invited. ++ */ ++ public void invite(Message message, String user, String reason) { ++ // TODO listen for 404 error code when inviter supplies a non-existent JID ++ message.setTo(room); ++ ++ // Create the MUCUser packet that will include the invitation ++ MUCUser mucUser = new MUCUser(); ++ MUCUser.Invite invite = new MUCUser.Invite(); ++ invite.setTo(user); ++ invite.setReason(reason); ++ mucUser.setInvite(invite); ++ // Add the MUCUser packet that includes the invitation to the message ++ message.addExtension(mucUser); ++ ++ connection.sendPacket(message); ++ } ++ ++ /** ++ * Informs the sender of an invitation that the invitee declines the invitation. The rejection ++ * will be sent to the room which in turn will forward the rejection to the inviter. ++ * ++ * @param conn the connection to use for sending the rejection. ++ * @param room the room that sent the original invitation. ++ * @param inviter the inviter of the declined invitation. ++ * @param reason the reason why the invitee is declining the invitation. ++ */ ++ public static void decline(Connection conn, String room, String inviter, String reason) { ++ Message message = new Message(room); ++ ++ // Create the MUCUser packet that will include the rejection ++ MUCUser mucUser = new MUCUser(); ++ MUCUser.Decline decline = new MUCUser.Decline(); ++ decline.setTo(inviter); ++ decline.setReason(reason); ++ mucUser.setDecline(decline); ++ // Add the MUCUser packet that includes the rejection ++ message.addExtension(mucUser); ++ ++ conn.sendPacket(message); ++ } ++ ++ /** ++ * Adds a listener to invitation notifications. The listener will be fired anytime ++ * an invitation is received. ++ * ++ * @param conn the connection where the listener will be applied. ++ * @param listener an invitation listener. ++ */ ++ public static void addInvitationListener(Connection conn, InvitationListener listener) { ++ InvitationsMonitor.getInvitationsMonitor(conn).addInvitationListener(listener); ++ } ++ ++ /** ++ * Removes a listener to invitation notifications. The listener will be fired anytime ++ * an invitation is received. ++ * ++ * @param conn the connection where the listener was applied. ++ * @param listener an invitation listener. ++ */ ++ public static void removeInvitationListener(Connection conn, InvitationListener listener) { ++ InvitationsMonitor.getInvitationsMonitor(conn).removeInvitationListener(listener); ++ } ++ ++ /** ++ * Adds a listener to invitation rejections notifications. The listener will be fired anytime ++ * an invitation is declined. ++ * ++ * @param listener an invitation rejection listener. ++ */ ++ public void addInvitationRejectionListener(InvitationRejectionListener listener) { ++ synchronized (invitationRejectionListeners) { ++ if (!invitationRejectionListeners.contains(listener)) { ++ invitationRejectionListeners.add(listener); ++ } ++ } ++ } ++ ++ /** ++ * Removes a listener from invitation rejections notifications. The listener will be fired ++ * anytime an invitation is declined. ++ * ++ * @param listener an invitation rejection listener. ++ */ ++ public void removeInvitationRejectionListener(InvitationRejectionListener listener) { ++ synchronized (invitationRejectionListeners) { ++ invitationRejectionListeners.remove(listener); ++ } ++ } ++ ++ /** ++ * Fires invitation rejection listeners. ++ * ++ * @param invitee the user being invited. ++ * @param reason the reason for the rejection ++ */ ++ private void fireInvitationRejectionListeners(String invitee, String reason) { ++ InvitationRejectionListener[] listeners; ++ synchronized (invitationRejectionListeners) { ++ listeners = new InvitationRejectionListener[invitationRejectionListeners.size()]; ++ invitationRejectionListeners.toArray(listeners); ++ } ++ for (InvitationRejectionListener listener : listeners) { ++ listener.invitationDeclined(invitee, reason); ++ } ++ } ++ ++ /** ++ * Adds a listener to subject change notifications. The listener will be fired anytime ++ * the room's subject changes. ++ * ++ * @param listener a subject updated listener. ++ */ ++ public void addSubjectUpdatedListener(SubjectUpdatedListener listener) { ++ synchronized (subjectUpdatedListeners) { ++ if (!subjectUpdatedListeners.contains(listener)) { ++ subjectUpdatedListeners.add(listener); ++ } ++ } ++ } ++ ++ /** ++ * Removes a listener from subject change notifications. The listener will be fired ++ * anytime the room's subject changes. ++ * ++ * @param listener a subject updated listener. ++ */ ++ public void removeSubjectUpdatedListener(SubjectUpdatedListener listener) { ++ synchronized (subjectUpdatedListeners) { ++ subjectUpdatedListeners.remove(listener); ++ } ++ } ++ ++ /** ++ * Fires subject updated listeners. ++ */ ++ private void fireSubjectUpdatedListeners(String subject, String from) { ++ SubjectUpdatedListener[] listeners; ++ synchronized (subjectUpdatedListeners) { ++ listeners = new SubjectUpdatedListener[subjectUpdatedListeners.size()]; ++ subjectUpdatedListeners.toArray(listeners); ++ } ++ for (SubjectUpdatedListener listener : listeners) { ++ listener.subjectUpdated(subject, from); ++ } ++ } ++ ++ /** ++ * Adds a new {@link PacketInterceptor} that will be invoked every time a new presence ++ * is going to be sent by this MultiUserChat to the server. Packet interceptors may ++ * add new extensions to the presence that is going to be sent to the MUC service. ++ * ++ * @param presenceInterceptor the new packet interceptor that will intercept presence packets. ++ */ ++ public void addPresenceInterceptor(PacketInterceptor presenceInterceptor) { ++ presenceInterceptors.add(presenceInterceptor); ++ } ++ ++ /** ++ * Removes a {@link PacketInterceptor} that was being invoked every time a new presence ++ * was being sent by this MultiUserChat to the server. Packet interceptors may ++ * add new extensions to the presence that is going to be sent to the MUC service. ++ * ++ * @param presenceInterceptor the packet interceptor to remove. ++ */ ++ public void removePresenceInterceptor(PacketInterceptor presenceInterceptor) { ++ presenceInterceptors.remove(presenceInterceptor); ++ } ++ ++ /** ++ * Returns the last known room's subject or <tt>null</tt> if the user hasn't joined the room ++ * or the room does not have a subject yet. In case the room has a subject, as soon as the ++ * user joins the room a message with the current room's subject will be received.<p> ++ * ++ * To be notified every time the room's subject change you should add a listener ++ * to this room. {@link #addSubjectUpdatedListener(SubjectUpdatedListener)}<p> ++ * ++ * To change the room's subject use {@link #changeSubject(String)}. ++ * ++ * @return the room's subject or <tt>null</tt> if the user hasn't joined the room or the ++ * room does not have a subject yet. ++ */ ++ public String getSubject() { ++ return subject; ++ } ++ ++ /** ++ * Returns the reserved room nickname for the user in the room. A user may have a reserved ++ * nickname, for example through explicit room registration or database integration. In such ++ * cases it may be desirable for the user to discover the reserved nickname before attempting ++ * to enter the room. ++ * ++ * @return the reserved room nickname or <tt>null</tt> if none. ++ */ ++ public String getReservedNickname() { ++ try { ++ DiscoverInfo result = ++ ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo( ++ room, ++ "x-roomuser-item"); ++ // Look for an Identity that holds the reserved nickname and return its name ++ for (Iterator<DiscoverInfo.Identity> identities = result.getIdentities(); ++ identities.hasNext();) { ++ DiscoverInfo.Identity identity = identities.next(); ++ return identity.getName(); ++ } ++ // If no Identity was found then the user does not have a reserved room nickname ++ return null; ++ } ++ catch (XMPPException e) { ++ e.printStackTrace(); ++ return null; ++ } ++ } ++ ++ /** ++ * Returns the nickname that was used to join the room, or <tt>null</tt> if not ++ * currently joined. ++ * ++ * @return the nickname currently being used. ++ */ ++ public String getNickname() { ++ return nickname; ++ } ++ ++ /** ++ * Changes the occupant's nickname to a new nickname within the room. Each room occupant ++ * will receive two presence packets. One of type "unavailable" for the old nickname and one ++ * indicating availability for the new nickname. The unavailable presence will contain the new ++ * nickname and an appropriate status code (namely 303) as extended presence information. The ++ * status code 303 indicates that the occupant is changing his/her nickname. ++ * ++ * @param nickname the new nickname within the room. ++ * @throws XMPPException if the new nickname is already in use by another occupant. ++ */ ++ public void changeNickname(String nickname) throws XMPPException { ++ if (nickname == null || nickname.equals("")) { ++ throw new IllegalArgumentException("Nickname must not be null or blank."); ++ } ++ // Check that we already have joined the room before attempting to change the ++ // nickname. ++ if (!joined) { ++ throw new IllegalStateException("Must be logged into the room to change nickname."); ++ } ++ // We change the nickname by sending a presence packet where the "to" ++ // field is in the form "roomName@service/nickname" ++ // We don't have to signal the MUC support again ++ Presence joinPresence = new Presence(Presence.Type.available); ++ joinPresence.setTo(room + "/" + nickname); ++ // Invoke presence interceptors so that extra information can be dynamically added ++ for (PacketInterceptor packetInterceptor : presenceInterceptors) { ++ packetInterceptor.interceptPacket(joinPresence); ++ } ++ ++ // Wait for a presence packet back from the server. ++ PacketFilter responseFilter = ++ new AndFilter( ++ new FromMatchesFilter(room + "/" + nickname), ++ new PacketTypeFilter(Presence.class)); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Send join packet. ++ connection.sendPacket(joinPresence); ++ // Wait up to a certain number of seconds for a reply. ++ Presence presence = ++ (Presence) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (presence == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (presence.getError() != null) { ++ throw new XMPPException(presence.getError()); ++ } ++ this.nickname = nickname; ++ } ++ ++ /** ++ * Changes the occupant's availability status within the room. The presence type ++ * will remain available but with a new status that describes the presence update and ++ * a new presence mode (e.g. Extended away). ++ * ++ * @param status a text message describing the presence update. ++ * @param mode the mode type for the presence update. ++ */ ++ public void changeAvailabilityStatus(String status, Presence.Mode mode) { ++ if (nickname == null || nickname.equals("")) { ++ throw new IllegalArgumentException("Nickname must not be null or blank."); ++ } ++ // Check that we already have joined the room before attempting to change the ++ // availability status. ++ if (!joined) { ++ throw new IllegalStateException( ++ "Must be logged into the room to change the " + "availability status."); ++ } ++ // We change the availability status by sending a presence packet to the room with the ++ // new presence status and mode ++ Presence joinPresence = new Presence(Presence.Type.available); ++ joinPresence.setStatus(status); ++ joinPresence.setMode(mode); ++ joinPresence.setTo(room + "/" + nickname); ++ // Invoke presence interceptors so that extra information can be dynamically added ++ for (PacketInterceptor packetInterceptor : presenceInterceptors) { ++ packetInterceptor.interceptPacket(joinPresence); ++ } ++ ++ // Send join packet. ++ connection.sendPacket(joinPresence); ++ } ++ ++ /** ++ * Kicks a visitor or participant from the room. The kicked occupant will receive a presence ++ * of type "unavailable" including a status code 307 and optionally along with the reason ++ * (if provided) and the bare JID of the user who initiated the kick. After the occupant ++ * was kicked from the room, the rest of the occupants will receive a presence of type ++ * "unavailable". The presence will include a status code 307 which means that the occupant ++ * was kicked from the room. ++ * ++ * @param nickname the nickname of the participant or visitor to kick from the room ++ * (e.g. "john"). ++ * @param reason the reason why the participant or visitor is being kicked from the room. ++ * @throws XMPPException if an error occurs kicking the occupant. In particular, a ++ * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" ++ * was intended to be kicked (i.e. Not Allowed error); or a ++ * 403 error can occur if the occupant that intended to kick another occupant does ++ * not have kicking privileges (i.e. Forbidden error); or a ++ * 400 error can occur if the provided nickname is not present in the room. ++ */ ++ public void kickParticipant(String nickname, String reason) throws XMPPException { ++ changeRole(nickname, "none", reason); ++ } ++ ++ /** ++ * Grants voice to visitors in the room. In a moderated room, a moderator may want to manage ++ * who does and does not have "voice" in the room. To have voice means that a room occupant ++ * is able to send messages to the room occupants. ++ * ++ * @param nicknames the nicknames of the visitors to grant voice in the room (e.g. "john"). ++ * @throws XMPPException if an error occurs granting voice to a visitor. In particular, a ++ * 403 error can occur if the occupant that intended to grant voice is not ++ * a moderator in this room (i.e. Forbidden error); or a ++ * 400 error can occur if the provided nickname is not present in the room. ++ */ ++ public void grantVoice(Collection<String> nicknames) throws XMPPException { ++ changeRole(nicknames, "participant"); ++ } ++ ++ /** ++ * Grants voice to a visitor in the room. In a moderated room, a moderator may want to manage ++ * who does and does not have "voice" in the room. To have voice means that a room occupant ++ * is able to send messages to the room occupants. ++ * ++ * @param nickname the nickname of the visitor to grant voice in the room (e.g. "john"). ++ * @throws XMPPException if an error occurs granting voice to a visitor. In particular, a ++ * 403 error can occur if the occupant that intended to grant voice is not ++ * a moderator in this room (i.e. Forbidden error); or a ++ * 400 error can occur if the provided nickname is not present in the room. ++ */ ++ public void grantVoice(String nickname) throws XMPPException { ++ changeRole(nickname, "participant", null); ++ } ++ ++ /** ++ * Revokes voice from participants in the room. In a moderated room, a moderator may want to ++ * revoke an occupant's privileges to speak. To have voice means that a room occupant ++ * is able to send messages to the room occupants. ++ * ++ * @param nicknames the nicknames of the participants to revoke voice (e.g. "john"). ++ * @throws XMPPException if an error occurs revoking voice from a participant. In particular, a ++ * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" ++ * was tried to revoke his voice (i.e. Not Allowed error); or a ++ * 400 error can occur if the provided nickname is not present in the room. ++ */ ++ public void revokeVoice(Collection<String> nicknames) throws XMPPException { ++ changeRole(nicknames, "visitor"); ++ } ++ ++ /** ++ * Revokes voice from a participant in the room. In a moderated room, a moderator may want to ++ * revoke an occupant's privileges to speak. To have voice means that a room occupant ++ * is able to send messages to the room occupants. ++ * ++ * @param nickname the nickname of the participant to revoke voice (e.g. "john"). ++ * @throws XMPPException if an error occurs revoking voice from a participant. In particular, a ++ * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" ++ * was tried to revoke his voice (i.e. Not Allowed error); or a ++ * 400 error can occur if the provided nickname is not present in the room. ++ */ ++ public void revokeVoice(String nickname) throws XMPPException { ++ changeRole(nickname, "visitor", null); ++ } ++ ++ /** ++ * Bans users from the room. An admin or owner of the room can ban users from a room. This ++ * means that the banned user will no longer be able to join the room unless the ban has been ++ * removed. If the banned user was present in the room then he/she will be removed from the ++ * room and notified that he/she was banned along with the reason (if provided) and the bare ++ * XMPP user ID of the user who initiated the ban. ++ * ++ * @param jids the bare XMPP user IDs of the users to ban. ++ * @throws XMPPException if an error occurs banning a user. In particular, a ++ * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" ++ * was tried to be banned (i.e. Not Allowed error). ++ */ ++ public void banUsers(Collection<String> jids) throws XMPPException { ++ changeAffiliationByAdmin(jids, "outcast"); ++ } ++ ++ /** ++ * Bans a user from the room. An admin or owner of the room can ban users from a room. This ++ * means that the banned user will no longer be able to join the room unless the ban has been ++ * removed. If the banned user was present in the room then he/she will be removed from the ++ * room and notified that he/she was banned along with the reason (if provided) and the bare ++ * XMPP user ID of the user who initiated the ban. ++ * ++ * @param jid the bare XMPP user ID of the user to ban (e.g. "user@host.org"). ++ * @param reason the reason why the user was banned. ++ * @throws XMPPException if an error occurs banning a user. In particular, a ++ * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" ++ * was tried to be banned (i.e. Not Allowed error). ++ */ ++ public void banUser(String jid, String reason) throws XMPPException { ++ changeAffiliationByAdmin(jid, "outcast", reason); ++ } ++ ++ /** ++ * Grants membership to other users. Only administrators are able to grant membership. A user ++ * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room ++ * that a user cannot enter without being on the member list). ++ * ++ * @param jids the XMPP user IDs of the users to grant membership. ++ * @throws XMPPException if an error occurs granting membership to a user. ++ */ ++ public void grantMembership(Collection<String> jids) throws XMPPException { ++ changeAffiliationByAdmin(jids, "member"); ++ } ++ ++ /** ++ * Grants membership to a user. Only administrators are able to grant membership. A user ++ * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room ++ * that a user cannot enter without being on the member list). ++ * ++ * @param jid the XMPP user ID of the user to grant membership (e.g. "user@host.org"). ++ * @throws XMPPException if an error occurs granting membership to a user. ++ */ ++ public void grantMembership(String jid) throws XMPPException { ++ changeAffiliationByAdmin(jid, "member", null); ++ } ++ ++ /** ++ * Revokes users' membership. Only administrators are able to revoke membership. A user ++ * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room ++ * that a user cannot enter without being on the member list). If the user is in the room and ++ * the room is of type members-only then the user will be removed from the room. ++ * ++ * @param jids the bare XMPP user IDs of the users to revoke membership. ++ * @throws XMPPException if an error occurs revoking membership to a user. ++ */ ++ public void revokeMembership(Collection<String> jids) throws XMPPException { ++ changeAffiliationByAdmin(jids, "none"); ++ } ++ ++ /** ++ * Revokes a user's membership. Only administrators are able to revoke membership. A user ++ * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room ++ * that a user cannot enter without being on the member list). If the user is in the room and ++ * the room is of type members-only then the user will be removed from the room. ++ * ++ * @param jid the bare XMPP user ID of the user to revoke membership (e.g. "user@host.org"). ++ * @throws XMPPException if an error occurs revoking membership to a user. ++ */ ++ public void revokeMembership(String jid) throws XMPPException { ++ changeAffiliationByAdmin(jid, "none", null); ++ } ++ ++ /** ++ * Grants moderator privileges to participants or visitors. Room administrators may grant ++ * moderator privileges. A moderator is allowed to kick users, grant and revoke voice, invite ++ * other users, modify room's subject plus all the partcipants privileges. ++ * ++ * @param nicknames the nicknames of the occupants to grant moderator privileges. ++ * @throws XMPPException if an error occurs granting moderator privileges to a user. ++ */ ++ public void grantModerator(Collection<String> nicknames) throws XMPPException { ++ changeRole(nicknames, "moderator"); ++ } ++ ++ /** ++ * Grants moderator privileges to a participant or visitor. Room administrators may grant ++ * moderator privileges. A moderator is allowed to kick users, grant and revoke voice, invite ++ * other users, modify room's subject plus all the partcipants privileges. ++ * ++ * @param nickname the nickname of the occupant to grant moderator privileges. ++ * @throws XMPPException if an error occurs granting moderator privileges to a user. ++ */ ++ public void grantModerator(String nickname) throws XMPPException { ++ changeRole(nickname, "moderator", null); ++ } ++ ++ /** ++ * Revokes moderator privileges from other users. The occupant that loses moderator ++ * privileges will become a participant. Room administrators may revoke moderator privileges ++ * only to occupants whose affiliation is member or none. This means that an administrator is ++ * not allowed to revoke moderator privileges from other room administrators or owners. ++ * ++ * @param nicknames the nicknames of the occupants to revoke moderator privileges. ++ * @throws XMPPException if an error occurs revoking moderator privileges from a user. ++ */ ++ public void revokeModerator(Collection<String> nicknames) throws XMPPException { ++ changeRole(nicknames, "participant"); ++ } ++ ++ /** ++ * Revokes moderator privileges from another user. The occupant that loses moderator ++ * privileges will become a participant. Room administrators may revoke moderator privileges ++ * only to occupants whose affiliation is member or none. This means that an administrator is ++ * not allowed to revoke moderator privileges from other room administrators or owners. ++ * ++ * @param nickname the nickname of the occupant to revoke moderator privileges. ++ * @throws XMPPException if an error occurs revoking moderator privileges from a user. ++ */ ++ public void revokeModerator(String nickname) throws XMPPException { ++ changeRole(nickname, "participant", null); ++ } ++ ++ /** ++ * Grants ownership privileges to other users. Room owners may grant ownership privileges. ++ * Some room implementations will not allow to grant ownership privileges to other users. ++ * An owner is allowed to change defining room features as well as perform all administrative ++ * functions. ++ * ++ * @param jids the collection of bare XMPP user IDs of the users to grant ownership. ++ * @throws XMPPException if an error occurs granting ownership privileges to a user. ++ */ ++ public void grantOwnership(Collection<String> jids) throws XMPPException { ++ changeAffiliationByAdmin(jids, "owner"); ++ } ++ ++ /** ++ * Grants ownership privileges to another user. Room owners may grant ownership privileges. ++ * Some room implementations will not allow to grant ownership privileges to other users. ++ * An owner is allowed to change defining room features as well as perform all administrative ++ * functions. ++ * ++ * @param jid the bare XMPP user ID of the user to grant ownership (e.g. "user@host.org"). ++ * @throws XMPPException if an error occurs granting ownership privileges to a user. ++ */ ++ public void grantOwnership(String jid) throws XMPPException { ++ changeAffiliationByAdmin(jid, "owner", null); ++ } ++ ++ /** ++ * Revokes ownership privileges from other users. The occupant that loses ownership ++ * privileges will become an administrator. Room owners may revoke ownership privileges. ++ * Some room implementations will not allow to grant ownership privileges to other users. ++ * ++ * @param jids the bare XMPP user IDs of the users to revoke ownership. ++ * @throws XMPPException if an error occurs revoking ownership privileges from a user. ++ */ ++ public void revokeOwnership(Collection<String> jids) throws XMPPException { ++ changeAffiliationByAdmin(jids, "admin"); ++ } ++ ++ /** ++ * Revokes ownership privileges from another user. The occupant that loses ownership ++ * privileges will become an administrator. Room owners may revoke ownership privileges. ++ * Some room implementations will not allow to grant ownership privileges to other users. ++ * ++ * @param jid the bare XMPP user ID of the user to revoke ownership (e.g. "user@host.org"). ++ * @throws XMPPException if an error occurs revoking ownership privileges from a user. ++ */ ++ public void revokeOwnership(String jid) throws XMPPException { ++ changeAffiliationByAdmin(jid, "admin", null); ++ } ++ ++ /** ++ * Grants administrator privileges to other users. Room owners may grant administrator ++ * privileges to a member or unaffiliated user. An administrator is allowed to perform ++ * administrative functions such as banning users and edit moderator list. ++ * ++ * @param jids the bare XMPP user IDs of the users to grant administrator privileges. ++ * @throws XMPPException if an error occurs granting administrator privileges to a user. ++ */ ++ public void grantAdmin(Collection<String> jids) throws XMPPException { ++ changeAffiliationByOwner(jids, "admin"); ++ } ++ ++ /** ++ * Grants administrator privileges to another user. Room owners may grant administrator ++ * privileges to a member or unaffiliated user. An administrator is allowed to perform ++ * administrative functions such as banning users and edit moderator list. ++ * ++ * @param jid the bare XMPP user ID of the user to grant administrator privileges ++ * (e.g. "user@host.org"). ++ * @throws XMPPException if an error occurs granting administrator privileges to a user. ++ */ ++ public void grantAdmin(String jid) throws XMPPException { ++ changeAffiliationByOwner(jid, "admin"); ++ } ++ ++ /** ++ * Revokes administrator privileges from users. The occupant that loses administrator ++ * privileges will become a member. Room owners may revoke administrator privileges from ++ * a member or unaffiliated user. ++ * ++ * @param jids the bare XMPP user IDs of the user to revoke administrator privileges. ++ * @throws XMPPException if an error occurs revoking administrator privileges from a user. ++ */ ++ public void revokeAdmin(Collection<String> jids) throws XMPPException { ++ changeAffiliationByOwner(jids, "member"); ++ } ++ ++ /** ++ * Revokes administrator privileges from a user. The occupant that loses administrator ++ * privileges will become a member. Room owners may revoke administrator privileges from ++ * a member or unaffiliated user. ++ * ++ * @param jid the bare XMPP user ID of the user to revoke administrator privileges ++ * (e.g. "user@host.org"). ++ * @throws XMPPException if an error occurs revoking administrator privileges from a user. ++ */ ++ public void revokeAdmin(String jid) throws XMPPException { ++ changeAffiliationByOwner(jid, "member"); ++ } ++ ++ private void changeAffiliationByOwner(String jid, String affiliation) throws XMPPException { ++ MUCOwner iq = new MUCOwner(); ++ iq.setTo(room); ++ iq.setType(IQ.Type.SET); ++ // Set the new affiliation. ++ MUCOwner.Item item = new MUCOwner.Item(affiliation); ++ item.setJid(jid); ++ iq.addItem(item); ++ ++ // Wait for a response packet back from the server. ++ PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Send the change request to the server. ++ connection.sendPacket(iq); ++ // Wait up to a certain number of seconds for a reply. ++ IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (answer == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (answer.getError() != null) { ++ throw new XMPPException(answer.getError()); ++ } ++ } ++ ++ private void changeAffiliationByOwner(Collection<String> jids, String affiliation) ++ throws XMPPException { ++ MUCOwner iq = new MUCOwner(); ++ iq.setTo(room); ++ iq.setType(IQ.Type.SET); ++ for (String jid : jids) { ++ // Set the new affiliation. ++ MUCOwner.Item item = new MUCOwner.Item(affiliation); ++ item.setJid(jid); ++ iq.addItem(item); ++ } ++ ++ // Wait for a response packet back from the server. ++ PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Send the change request to the server. ++ connection.sendPacket(iq); ++ // Wait up to a certain number of seconds for a reply. ++ IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (answer == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (answer.getError() != null) { ++ throw new XMPPException(answer.getError()); ++ } ++ } ++ ++ private void changeAffiliationByAdmin(String jid, String affiliation, String reason) ++ throws XMPPException { ++ MUCAdmin iq = new MUCAdmin(); ++ iq.setTo(room); ++ iq.setType(IQ.Type.SET); ++ // Set the new affiliation. ++ MUCAdmin.Item item = new MUCAdmin.Item(affiliation, null); ++ item.setJid(jid); ++ if(reason != null) ++ item.setReason(reason); ++ iq.addItem(item); ++ ++ // Wait for a response packet back from the server. ++ PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Send the change request to the server. ++ connection.sendPacket(iq); ++ // Wait up to a certain number of seconds for a reply. ++ IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (answer == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (answer.getError() != null) { ++ throw new XMPPException(answer.getError()); ++ } ++ } ++ ++ private void changeAffiliationByAdmin(Collection<String> jids, String affiliation) ++ throws XMPPException { ++ MUCAdmin iq = new MUCAdmin(); ++ iq.setTo(room); ++ iq.setType(IQ.Type.SET); ++ for (String jid : jids) { ++ // Set the new affiliation. ++ MUCAdmin.Item item = new MUCAdmin.Item(affiliation, null); ++ item.setJid(jid); ++ iq.addItem(item); ++ } ++ ++ // Wait for a response packet back from the server. ++ PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Send the change request to the server. ++ connection.sendPacket(iq); ++ // Wait up to a certain number of seconds for a reply. ++ IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (answer == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (answer.getError() != null) { ++ throw new XMPPException(answer.getError()); ++ } ++ } ++ ++ private void changeRole(String nickname, String role, String reason) throws XMPPException { ++ MUCAdmin iq = new MUCAdmin(); ++ iq.setTo(room); ++ iq.setType(IQ.Type.SET); ++ // Set the new role. ++ MUCAdmin.Item item = new MUCAdmin.Item(null, role); ++ item.setNick(nickname); ++ item.setReason(reason); ++ iq.addItem(item); ++ ++ // Wait for a response packet back from the server. ++ PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Send the change request to the server. ++ connection.sendPacket(iq); ++ // Wait up to a certain number of seconds for a reply. ++ IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (answer == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (answer.getError() != null) { ++ throw new XMPPException(answer.getError()); ++ } ++ } ++ ++ private void changeRole(Collection<String> nicknames, String role) throws XMPPException { ++ MUCAdmin iq = new MUCAdmin(); ++ iq.setTo(room); ++ iq.setType(IQ.Type.SET); ++ for (String nickname : nicknames) { ++ // Set the new role. ++ MUCAdmin.Item item = new MUCAdmin.Item(null, role); ++ item.setNick(nickname); ++ iq.addItem(item); ++ } ++ ++ // Wait for a response packet back from the server. ++ PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Send the change request to the server. ++ connection.sendPacket(iq); ++ // Wait up to a certain number of seconds for a reply. ++ IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (answer == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (answer.getError() != null) { ++ throw new XMPPException(answer.getError()); ++ } ++ } ++ ++ /** ++ * Returns the number of occupants in the group chat.<p> ++ * ++ * Note: this value will only be accurate after joining the group chat, and ++ * may fluctuate over time. If you query this value directly after joining the ++ * group chat it may not be accurate, as it takes a certain amount of time for ++ * the server to send all presence packets to this client. ++ * ++ * @return the number of occupants in the group chat. ++ */ ++ public int getOccupantsCount() { ++ return occupantsMap.size(); ++ } ++ ++ /** ++ * Returns an Iterator (of Strings) for the list of fully qualified occupants ++ * in the group chat. For example, "conference@chat.jivesoftware.com/SomeUser". ++ * Typically, a client would only display the nickname of the occupant. To ++ * get the nickname from the fully qualified name, use the ++ * {@link org.jivesoftware.smack.util.StringUtils#parseResource(String)} method. ++ * Note: this value will only be accurate after joining the group chat, and may ++ * fluctuate over time. ++ * ++ * @return an Iterator for the occupants in the group chat. ++ */ ++ public Iterator<String> getOccupants() { ++ return Collections.unmodifiableList(new ArrayList<String>(occupantsMap.keySet())) ++ .iterator(); ++ } ++ ++ /** ++ * Returns the presence info for a particular user, or <tt>null</tt> if the user ++ * is not in the room.<p> ++ * ++ * @param user the room occupant to search for his presence. The format of user must ++ * be: roomName@service/nickname (e.g. darkcave@macbeth.shakespeare.lit/thirdwitch). ++ * @return the occupant's current presence, or <tt>null</tt> if the user is unavailable ++ * or if no presence information is available. ++ */ ++ public Presence getOccupantPresence(String user) { ++ return occupantsMap.get(user); ++ } ++ ++ /** ++ * Returns the Occupant information for a particular occupant, or <tt>null</tt> if the ++ * user is not in the room. The Occupant object may include information such as full ++ * JID of the user as well as the role and affiliation of the user in the room.<p> ++ * ++ * @param user the room occupant to search for his presence. The format of user must ++ * be: roomName@service/nickname (e.g. darkcave@macbeth.shakespeare.lit/thirdwitch). ++ * @return the Occupant or <tt>null</tt> if the user is unavailable (i.e. not in the room). ++ */ ++ public Occupant getOccupant(String user) { ++ Presence presence = occupantsMap.get(user); ++ if (presence != null) { ++ return new Occupant(presence); ++ } ++ return null; ++ } ++ ++ /** ++ * Adds a packet listener that will be notified of any new Presence packets ++ * sent to the group chat. Using a listener is a suitable way to know when the list ++ * of occupants should be re-loaded due to any changes. ++ * ++ * @param listener a packet listener that will be notified of any presence packets ++ * sent to the group chat. ++ */ ++ public void addParticipantListener(PacketListener listener) { ++ connection.addPacketListener(listener, presenceFilter); ++ connectionListeners.add(listener); ++ } ++ ++ /** ++ * Remoces a packet listener that was being notified of any new Presence packets ++ * sent to the group chat. ++ * ++ * @param listener a packet listener that was being notified of any presence packets ++ * sent to the group chat. ++ */ ++ public void removeParticipantListener(PacketListener listener) { ++ connection.removePacketListener(listener); ++ connectionListeners.remove(listener); ++ } ++ ++ /** ++ * Returns a collection of <code>Affiliate</code> with the room owners. ++ * ++ * @return a collection of <code>Affiliate</code> with the room owners. ++ * @throws XMPPException if an error occured while performing the request to the server or you ++ * don't have enough privileges to get this information. ++ */ ++ public Collection<Affiliate> getOwners() throws XMPPException { ++ return getAffiliatesByAdmin("owner"); ++ } ++ ++ /** ++ * Returns a collection of <code>Affiliate</code> with the room administrators. ++ * ++ * @return a collection of <code>Affiliate</code> with the room administrators. ++ * @throws XMPPException if an error occured while performing the request to the server or you ++ * don't have enough privileges to get this information. ++ */ ++ public Collection<Affiliate> getAdmins() throws XMPPException { ++ return getAffiliatesByOwner("admin"); ++ } ++ ++ /** ++ * Returns a collection of <code>Affiliate</code> with the room members. ++ * ++ * @return a collection of <code>Affiliate</code> with the room members. ++ * @throws XMPPException if an error occured while performing the request to the server or you ++ * don't have enough privileges to get this information. ++ */ ++ public Collection<Affiliate> getMembers() throws XMPPException { ++ return getAffiliatesByAdmin("member"); ++ } ++ ++ /** ++ * Returns a collection of <code>Affiliate</code> with the room outcasts. ++ * ++ * @return a collection of <code>Affiliate</code> with the room outcasts. ++ * @throws XMPPException if an error occured while performing the request to the server or you ++ * don't have enough privileges to get this information. ++ */ ++ public Collection<Affiliate> getOutcasts() throws XMPPException { ++ return getAffiliatesByAdmin("outcast"); ++ } ++ ++ /** ++ * Returns a collection of <code>Affiliate</code> that have the specified room affiliation ++ * sending a request in the owner namespace. ++ * ++ * @param affiliation the affiliation of the users in the room. ++ * @return a collection of <code>Affiliate</code> that have the specified room affiliation. ++ * @throws XMPPException if an error occured while performing the request to the server or you ++ * don't have enough privileges to get this information. ++ */ ++ private Collection<Affiliate> getAffiliatesByOwner(String affiliation) throws XMPPException { ++ MUCOwner iq = new MUCOwner(); ++ iq.setTo(room); ++ iq.setType(IQ.Type.GET); ++ // Set the specified affiliation. This may request the list of owners/admins/members/outcasts. ++ MUCOwner.Item item = new MUCOwner.Item(affiliation); ++ iq.addItem(item); ++ ++ // Wait for a response packet back from the server. ++ PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Send the request to the server. ++ connection.sendPacket(iq); ++ // Wait up to a certain number of seconds for a reply. ++ MUCOwner answer = (MUCOwner) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (answer == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (answer.getError() != null) { ++ throw new XMPPException(answer.getError()); ++ } ++ // Get the list of affiliates from the server's answer ++ List<Affiliate> affiliates = new ArrayList<Affiliate>(); ++ for (Iterator it = answer.getItems(); it.hasNext();) { ++ affiliates.add(new Affiliate((MUCOwner.Item) it.next())); ++ } ++ return affiliates; ++ } ++ ++ /** ++ * Returns a collection of <code>Affiliate</code> that have the specified room affiliation ++ * sending a request in the admin namespace. ++ * ++ * @param affiliation the affiliation of the users in the room. ++ * @return a collection of <code>Affiliate</code> that have the specified room affiliation. ++ * @throws XMPPException if an error occured while performing the request to the server or you ++ * don't have enough privileges to get this information. ++ */ ++ private Collection<Affiliate> getAffiliatesByAdmin(String affiliation) throws XMPPException { ++ MUCAdmin iq = new MUCAdmin(); ++ iq.setTo(room); ++ iq.setType(IQ.Type.GET); ++ // Set the specified affiliation. This may request the list of owners/admins/members/outcasts. ++ MUCAdmin.Item item = new MUCAdmin.Item(affiliation, null); ++ iq.addItem(item); ++ ++ // Wait for a response packet back from the server. ++ PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Send the request to the server. ++ connection.sendPacket(iq); ++ // Wait up to a certain number of seconds for a reply. ++ MUCAdmin answer = (MUCAdmin) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (answer == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (answer.getError() != null) { ++ throw new XMPPException(answer.getError()); ++ } ++ // Get the list of affiliates from the server's answer ++ List<Affiliate> affiliates = new ArrayList<Affiliate>(); ++ for (Iterator it = answer.getItems(); it.hasNext();) { ++ affiliates.add(new Affiliate((MUCAdmin.Item) it.next())); ++ } ++ return affiliates; ++ } ++ ++ /** ++ * Returns a collection of <code>Occupant</code> with the room moderators. ++ * ++ * @return a collection of <code>Occupant</code> with the room moderators. ++ * @throws XMPPException if an error occured while performing the request to the server or you ++ * don't have enough privileges to get this information. ++ */ ++ public Collection<Occupant> getModerators() throws XMPPException { ++ return getOccupants("moderator"); ++ } ++ ++ /** ++ * Returns a collection of <code>Occupant</code> with the room participants. ++ * ++ * @return a collection of <code>Occupant</code> with the room participants. ++ * @throws XMPPException if an error occured while performing the request to the server or you ++ * don't have enough privileges to get this information. ++ */ ++ public Collection<Occupant> getParticipants() throws XMPPException { ++ return getOccupants("participant"); ++ } ++ ++ /** ++ * Returns a collection of <code>Occupant</code> that have the specified room role. ++ * ++ * @param role the role of the occupant in the room. ++ * @return a collection of <code>Occupant</code> that have the specified room role. ++ * @throws XMPPException if an error occured while performing the request to the server or you ++ * don't have enough privileges to get this information. ++ */ ++ private Collection<Occupant> getOccupants(String role) throws XMPPException { ++ MUCAdmin iq = new MUCAdmin(); ++ iq.setTo(room); ++ iq.setType(IQ.Type.GET); ++ // Set the specified role. This may request the list of moderators/participants. ++ MUCAdmin.Item item = new MUCAdmin.Item(null, role); ++ iq.addItem(item); ++ ++ // Wait for a response packet back from the server. ++ PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Send the request to the server. ++ connection.sendPacket(iq); ++ // Wait up to a certain number of seconds for a reply. ++ MUCAdmin answer = (MUCAdmin) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (answer == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (answer.getError() != null) { ++ throw new XMPPException(answer.getError()); ++ } ++ // Get the list of participants from the server's answer ++ List<Occupant> participants = new ArrayList<Occupant>(); ++ for (Iterator it = answer.getItems(); it.hasNext();) { ++ participants.add(new Occupant((MUCAdmin.Item) it.next())); ++ } ++ return participants; ++ } ++ ++ /** ++ * Sends a message to the chat room. ++ * ++ * @param text the text of the message to send. ++ * @throws XMPPException if sending the message fails. ++ */ ++ public void sendMessage(String text) throws XMPPException { ++ Message message = new Message(room, Message.Type.groupchat); ++ message.setBody(text); ++ connection.sendPacket(message); ++ } ++ ++ /** ++ * Returns a new Chat for sending private messages to a given room occupant. ++ * The Chat's occupant address is the room's JID (i.e. roomName@service/nick). The server ++ * service will change the 'from' address to the sender's room JID and delivering the message ++ * to the intended recipient's full JID. ++ * ++ * @param occupant occupant unique room JID (e.g. 'darkcave@macbeth.shakespeare.lit/Paul'). ++ * @param listener the listener is a message listener that will handle messages for the newly ++ * created chat. ++ * @return new Chat for sending private messages to a given room occupant. ++ */ ++ public Chat createPrivateChat(String occupant, MessageListener listener) { ++ return connection.getChatManager().createChat(occupant, listener); ++ } ++ ++ /** ++ * Creates a new Message to send to the chat room. ++ * ++ * @return a new Message addressed to the chat room. ++ */ ++ public Message createMessage() { ++ return new Message(room, Message.Type.groupchat); ++ } ++ ++ /** ++ * Sends a Message to the chat room. ++ * ++ * @param message the message. ++ * @throws XMPPException if sending the message fails. ++ */ ++ public void sendMessage(Message message) throws XMPPException { ++ connection.sendPacket(message); ++ } ++ ++ /** ++ * Polls for and returns the next message, or <tt>null</tt> if there isn't ++ * a message immediately available. This method provides significantly different ++ * functionalty than the {@link #nextMessage()} method since it's non-blocking. ++ * In other words, the method call will always return immediately, whereas the ++ * nextMessage method will return only when a message is available (or after ++ * a specific timeout). ++ * ++ * @return the next message if one is immediately available and ++ * <tt>null</tt> otherwise. ++ */ ++ public Message pollMessage() { ++ return (Message) messageCollector.pollResult(); ++ } ++ ++ /** ++ * Returns the next available message in the chat. The method call will block ++ * (not return) until a message is available. ++ * ++ * @return the next message. ++ */ ++ public Message nextMessage() { ++ return (Message) messageCollector.nextResult(); ++ } ++ ++ /** ++ * Returns the next available message in the chat. The method call will block ++ * (not return) until a packet is available or the <tt>timeout</tt> has elapased. ++ * If the timeout elapses without a result, <tt>null</tt> will be returned. ++ * ++ * @param timeout the maximum amount of time to wait for the next message. ++ * @return the next message, or <tt>null</tt> if the timeout elapses without a ++ * message becoming available. ++ */ ++ public Message nextMessage(long timeout) { ++ return (Message) messageCollector.nextResult(timeout); ++ } ++ ++ /** ++ * Adds a packet listener that will be notified of any new messages in the ++ * group chat. Only "group chat" messages addressed to this group chat will ++ * be delivered to the listener. If you wish to listen for other packets ++ * that may be associated with this group chat, you should register a ++ * PacketListener directly with the Connection with the appropriate ++ * PacketListener. ++ * ++ * @param listener a packet listener. ++ */ ++ public void addMessageListener(PacketListener listener) { ++ connection.addPacketListener(listener, messageFilter); ++ connectionListeners.add(listener); ++ } ++ ++ /** ++ * Removes a packet listener that was being notified of any new messages in the ++ * multi user chat. Only "group chat" messages addressed to this multi user chat were ++ * being delivered to the listener. ++ * ++ * @param listener a packet listener. ++ */ ++ public void removeMessageListener(PacketListener listener) { ++ connection.removePacketListener(listener); ++ connectionListeners.remove(listener); ++ } ++ ++ /** ++ * Changes the subject within the room. As a default, only users with a role of "moderator" ++ * are allowed to change the subject in a room. Although some rooms may be configured to ++ * allow a mere participant or even a visitor to change the subject. ++ * ++ * @param subject the new room's subject to set. ++ * @throws XMPPException if someone without appropriate privileges attempts to change the ++ * room subject will throw an error with code 403 (i.e. Forbidden) ++ */ ++ public void changeSubject(final String subject) throws XMPPException { ++ Message message = new Message(room, Message.Type.groupchat); ++ message.setSubject(subject); ++ // Wait for an error or confirmation message back from the server. ++ PacketFilter responseFilter = ++ new AndFilter( ++ new FromMatchesFilter(room), ++ new PacketTypeFilter(Message.class)); ++ responseFilter = new AndFilter(responseFilter, new PacketFilter() { ++ public boolean accept(Packet packet) { ++ Message msg = (Message) packet; ++ return subject.equals(msg.getSubject()); ++ } ++ }); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ // Send change subject packet. ++ connection.sendPacket(message); ++ // Wait up to a certain number of seconds for a reply. ++ Message answer = ++ (Message) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ // Stop queuing results ++ response.cancel(); ++ ++ if (answer == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (answer.getError() != null) { ++ throw new XMPPException(answer.getError()); ++ } ++ } ++ ++ /** ++ * Notification message that the user has joined the room. ++ */ ++ private synchronized void userHasJoined() { ++ // Update the list of joined rooms through this connection ++ List<String> rooms = joinedRooms.get(connection); ++ if (rooms == null) { ++ rooms = new ArrayList<String>(); ++ joinedRooms.put(connection, rooms); ++ } ++ rooms.add(room); ++ } ++ ++ /** ++ * Notification message that the user has left the room. ++ */ ++ private synchronized void userHasLeft() { ++ // Update the list of joined rooms through this connection ++ List<String> rooms = joinedRooms.get(connection); ++ if (rooms == null) { ++ return; ++ } ++ rooms.remove(room); ++ } ++ ++ /** ++ * Returns the MUCUser packet extension included in the packet or <tt>null</tt> if none. ++ * ++ * @param packet the packet that may include the MUCUser extension. ++ * @return the MUCUser found in the packet. ++ */ ++ private MUCUser getMUCUserExtension(Packet packet) { ++ if (packet != null) { ++ // Get the MUC User extension ++ return (MUCUser) packet.getExtension("x", "http://jabber.org/protocol/muc#user"); ++ } ++ return null; ++ } ++ ++ /** ++ * Adds a listener that will be notified of changes in your status in the room ++ * such as the user being kicked, banned, or granted admin permissions. ++ * ++ * @param listener a user status listener. ++ */ ++ public void addUserStatusListener(UserStatusListener listener) { ++ synchronized (userStatusListeners) { ++ if (!userStatusListeners.contains(listener)) { ++ userStatusListeners.add(listener); ++ } ++ } ++ } ++ ++ /** ++ * Removes a listener that was being notified of changes in your status in the room ++ * such as the user being kicked, banned, or granted admin permissions. ++ * ++ * @param listener a user status listener. ++ */ ++ public void removeUserStatusListener(UserStatusListener listener) { ++ synchronized (userStatusListeners) { ++ userStatusListeners.remove(listener); ++ } ++ } ++ ++ private void fireUserStatusListeners(String methodName, Object[] params) { ++ UserStatusListener[] listeners; ++ synchronized (userStatusListeners) { ++ listeners = new UserStatusListener[userStatusListeners.size()]; ++ userStatusListeners.toArray(listeners); ++ } ++ // Get the classes of the method parameters ++ Class[] paramClasses = new Class[params.length]; ++ for (int i = 0; i < params.length; i++) { ++ paramClasses[i] = params[i].getClass(); ++ } ++ try { ++ // Get the method to execute based on the requested methodName and parameters classes ++ Method method = UserStatusListener.class.getDeclaredMethod(methodName, paramClasses); ++ for (UserStatusListener listener : listeners) { ++ method.invoke(listener, params); ++ } ++ } catch (NoSuchMethodException e) { ++ e.printStackTrace(); ++ } catch (InvocationTargetException e) { ++ e.printStackTrace(); ++ } catch (IllegalAccessException e) { ++ e.printStackTrace(); ++ } ++ } ++ ++ /** ++ * Adds a listener that will be notified of changes in occupants status in the room ++ * such as the user being kicked, banned, or granted admin permissions. ++ * ++ * @param listener a participant status listener. ++ */ ++ public void addParticipantStatusListener(ParticipantStatusListener listener) { ++ synchronized (participantStatusListeners) { ++ if (!participantStatusListeners.contains(listener)) { ++ participantStatusListeners.add(listener); ++ } ++ } ++ } ++ ++ /** ++ * Removes a listener that was being notified of changes in occupants status in the room ++ * such as the user being kicked, banned, or granted admin permissions. ++ * ++ * @param listener a participant status listener. ++ */ ++ public void removeParticipantStatusListener(ParticipantStatusListener listener) { ++ synchronized (participantStatusListeners) { ++ participantStatusListeners.remove(listener); ++ } ++ } ++ ++ private void fireParticipantStatusListeners(String methodName, List<String> params) { ++ ParticipantStatusListener[] listeners; ++ synchronized (participantStatusListeners) { ++ listeners = new ParticipantStatusListener[participantStatusListeners.size()]; ++ participantStatusListeners.toArray(listeners); ++ } ++ try { ++ // Get the method to execute based on the requested methodName and parameter ++ Class[] classes = new Class[params.size()]; ++ for (int i=0;i<params.size(); i++) { ++ classes[i] = String.class; ++ } ++ Method method = ParticipantStatusListener.class.getDeclaredMethod(methodName, classes); ++ for (ParticipantStatusListener listener : listeners) { ++ method.invoke(listener, params.toArray()); ++ } ++ } catch (NoSuchMethodException e) { ++ e.printStackTrace(); ++ } catch (InvocationTargetException e) { ++ e.printStackTrace(); ++ } catch (IllegalAccessException e) { ++ e.printStackTrace(); ++ } ++ } ++ ++ private void init() { ++ // Create filters ++ messageFilter = ++ new AndFilter( ++ new FromMatchesFilter(room), ++ new MessageTypeFilter(Message.Type.groupchat)); ++ messageFilter = new AndFilter(messageFilter, new PacketFilter() { ++ public boolean accept(Packet packet) { ++ Message msg = (Message) packet; ++ return msg.getBody() != null; ++ } ++ }); ++ presenceFilter = ++ new AndFilter(new FromMatchesFilter(room), new PacketTypeFilter(Presence.class)); ++ ++ // Create a collector for incoming messages. ++ messageCollector = new ConnectionDetachedPacketCollector(); ++ ++ // Create a listener for subject updates. ++ PacketListener subjectListener = new PacketListener() { ++ public void processPacket(Packet packet) { ++ Message msg = (Message) packet; ++ // Update the room subject ++ subject = msg.getSubject(); ++ // Fire event for subject updated listeners ++ fireSubjectUpdatedListeners( ++ msg.getSubject(), ++ msg.getFrom()); ++ ++ } ++ }; ++ ++ // Create a listener for all presence updates. ++ PacketListener presenceListener = new PacketListener() { ++ public void processPacket(Packet packet) { ++ Presence presence = (Presence) packet; ++ String from = presence.getFrom(); ++ String myRoomJID = room + "/" + nickname; ++ boolean isUserStatusModification = presence.getFrom().equals(myRoomJID); ++ if (presence.getType() == Presence.Type.available) { ++ Presence oldPresence = occupantsMap.put(from, presence); ++ if (oldPresence != null) { ++ // Get the previous occupant's affiliation & role ++ MUCUser mucExtension = getMUCUserExtension(oldPresence); ++ String oldAffiliation = mucExtension.getItem().getAffiliation(); ++ String oldRole = mucExtension.getItem().getRole(); ++ // Get the new occupant's affiliation & role ++ mucExtension = getMUCUserExtension(presence); ++ String newAffiliation = mucExtension.getItem().getAffiliation(); ++ String newRole = mucExtension.getItem().getRole(); ++ // Fire role modification events ++ checkRoleModifications(oldRole, newRole, isUserStatusModification, from); ++ // Fire affiliation modification events ++ checkAffiliationModifications( ++ oldAffiliation, ++ newAffiliation, ++ isUserStatusModification, ++ from); ++ } ++ else { ++ // A new occupant has joined the room ++ if (!isUserStatusModification) { ++ List<String> params = new ArrayList<String>(); ++ params.add(from); ++ fireParticipantStatusListeners("joined", params); ++ } ++ } ++ } ++ else if (presence.getType() == Presence.Type.unavailable) { ++ occupantsMap.remove(from); ++ MUCUser mucUser = getMUCUserExtension(presence); ++ if (mucUser != null && mucUser.getStatus() != null) { ++ // Fire events according to the received presence code ++ checkPresenceCode( ++ mucUser.getStatus().getCode(), ++ presence.getFrom().equals(myRoomJID), ++ mucUser, ++ from); ++ } else { ++ // An occupant has left the room ++ if (!isUserStatusModification) { ++ List<String> params = new ArrayList<String>(); ++ params.add(from); ++ fireParticipantStatusListeners("left", params); ++ } ++ } ++ } ++ } ++ }; ++ ++ // Listens for all messages that include a MUCUser extension and fire the invitation ++ // rejection listeners if the message includes an invitation rejection. ++ PacketListener declinesListener = new PacketListener() { ++ public void processPacket(Packet packet) { ++ // Get the MUC User extension ++ MUCUser mucUser = getMUCUserExtension(packet); ++ // Check if the MUCUser informs that the invitee has declined the invitation ++ if (mucUser.getDecline() != null && ++ ((Message) packet).getType() != Message.Type.error) { ++ // Fire event for invitation rejection listeners ++ fireInvitationRejectionListeners( ++ mucUser.getDecline().getFrom(), ++ mucUser.getDecline().getReason()); ++ } ++ } ++ }; ++ ++ PacketMultiplexListener packetMultiplexor = new PacketMultiplexListener( ++ messageCollector, presenceListener, subjectListener, ++ declinesListener); ++ ++ roomListenerMultiplexor = RoomListenerMultiplexor.getRoomMultiplexor(connection); ++ ++ roomListenerMultiplexor.addRoom(room, packetMultiplexor); ++ } ++ ++ /** ++ * Fires notification events if the role of a room occupant has changed. If the occupant that ++ * changed his role is your occupant then the <code>UserStatusListeners</code> added to this ++ * <code>MultiUserChat</code> will be fired. On the other hand, if the occupant that changed ++ * his role is not yours then the <code>ParticipantStatusListeners</code> added to this ++ * <code>MultiUserChat</code> will be fired. The following table shows the events that will ++ * be fired depending on the previous and new role of the occupant. ++ * ++ * <pre> ++ * <table border="1"> ++ * <tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr> ++ * ++ * <tr><td>None</td><td>Visitor</td><td>--</td></tr> ++ * <tr><td>Visitor</td><td>Participant</td><td>voiceGranted</td></tr> ++ * <tr><td>Participant</td><td>Moderator</td><td>moderatorGranted</td></tr> ++ * ++ * <tr><td>None</td><td>Participant</td><td>voiceGranted</td></tr> ++ * <tr><td>None</td><td>Moderator</td><td>voiceGranted + moderatorGranted</td></tr> ++ * <tr><td>Visitor</td><td>Moderator</td><td>voiceGranted + moderatorGranted</td></tr> ++ * ++ * <tr><td>Moderator</td><td>Participant</td><td>moderatorRevoked</td></tr> ++ * <tr><td>Participant</td><td>Visitor</td><td>voiceRevoked</td></tr> ++ * <tr><td>Visitor</td><td>None</td><td>kicked</td></tr> ++ * ++ * <tr><td>Moderator</td><td>Visitor</td><td>voiceRevoked + moderatorRevoked</td></tr> ++ * <tr><td>Moderator</td><td>None</td><td>kicked</td></tr> ++ * <tr><td>Participant</td><td>None</td><td>kicked</td></tr> ++ * </table> ++ * </pre> ++ * ++ * @param oldRole the previous role of the user in the room before receiving the new presence ++ * @param newRole the new role of the user in the room after receiving the new presence ++ * @param isUserModification whether the received presence is about your user in the room or not ++ * @param from the occupant whose role in the room has changed ++ * (e.g. room@conference.jabber.org/nick). ++ */ ++ private void checkRoleModifications( ++ String oldRole, ++ String newRole, ++ boolean isUserModification, ++ String from) { ++ // Voice was granted to a visitor ++ if (("visitor".equals(oldRole) || "none".equals(oldRole)) ++ && "participant".equals(newRole)) { ++ if (isUserModification) { ++ fireUserStatusListeners("voiceGranted", new Object[] {}); ++ } ++ else { ++ List<String> params = new ArrayList<String>(); ++ params.add(from); ++ fireParticipantStatusListeners("voiceGranted", params); ++ } ++ } ++ // The participant's voice was revoked from the room ++ else if ( ++ "participant".equals(oldRole) ++ && ("visitor".equals(newRole) || "none".equals(newRole))) { ++ if (isUserModification) { ++ fireUserStatusListeners("voiceRevoked", new Object[] {}); ++ } ++ else { ++ List<String> params = new ArrayList<String>(); ++ params.add(from); ++ fireParticipantStatusListeners("voiceRevoked", params); ++ } ++ } ++ // Moderator privileges were granted to a participant ++ if (!"moderator".equals(oldRole) && "moderator".equals(newRole)) { ++ if ("visitor".equals(oldRole) || "none".equals(oldRole)) { ++ if (isUserModification) { ++ fireUserStatusListeners("voiceGranted", new Object[] {}); ++ } ++ else { ++ List<String> params = new ArrayList<String>(); ++ params.add(from); ++ fireParticipantStatusListeners("voiceGranted", params); ++ } ++ } ++ if (isUserModification) { ++ fireUserStatusListeners("moderatorGranted", new Object[] {}); ++ } ++ else { ++ List<String> params = new ArrayList<String>(); ++ params.add(from); ++ fireParticipantStatusListeners("moderatorGranted", params); ++ } ++ } ++ // Moderator privileges were revoked from a participant ++ else if ("moderator".equals(oldRole) && !"moderator".equals(newRole)) { ++ if ("visitor".equals(newRole) || "none".equals(newRole)) { ++ if (isUserModification) { ++ fireUserStatusListeners("voiceRevoked", new Object[] {}); ++ } ++ else { ++ List<String> params = new ArrayList<String>(); ++ params.add(from); ++ fireParticipantStatusListeners("voiceRevoked", params); ++ } ++ } ++ if (isUserModification) { ++ fireUserStatusListeners("moderatorRevoked", new Object[] {}); ++ } ++ else { ++ List<String> params = new ArrayList<String>(); ++ params.add(from); ++ fireParticipantStatusListeners("moderatorRevoked", params); ++ } ++ } ++ } ++ ++ /** ++ * Fires notification events if the affiliation of a room occupant has changed. If the ++ * occupant that changed his affiliation is your occupant then the ++ * <code>UserStatusListeners</code> added to this <code>MultiUserChat</code> will be fired. ++ * On the other hand, if the occupant that changed his affiliation is not yours then the ++ * <code>ParticipantStatusListeners</code> added to this <code>MultiUserChat</code> will be ++ * fired. The following table shows the events that will be fired depending on the previous ++ * and new affiliation of the occupant. ++ * ++ * <pre> ++ * <table border="1"> ++ * <tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr> ++ * ++ * <tr><td>None</td><td>Member</td><td>membershipGranted</td></tr> ++ * <tr><td>Member</td><td>Admin</td><td>membershipRevoked + adminGranted</td></tr> ++ * <tr><td>Admin</td><td>Owner</td><td>adminRevoked + ownershipGranted</td></tr> ++ * ++ * <tr><td>None</td><td>Admin</td><td>adminGranted</td></tr> ++ * <tr><td>None</td><td>Owner</td><td>ownershipGranted</td></tr> ++ * <tr><td>Member</td><td>Owner</td><td>membershipRevoked + ownershipGranted</td></tr> ++ * ++ * <tr><td>Owner</td><td>Admin</td><td>ownershipRevoked + adminGranted</td></tr> ++ * <tr><td>Admin</td><td>Member</td><td>adminRevoked + membershipGranted</td></tr> ++ * <tr><td>Member</td><td>None</td><td>membershipRevoked</td></tr> ++ * ++ * <tr><td>Owner</td><td>Member</td><td>ownershipRevoked + membershipGranted</td></tr> ++ * <tr><td>Owner</td><td>None</td><td>ownershipRevoked</td></tr> ++ * <tr><td>Admin</td><td>None</td><td>adminRevoked</td></tr> ++ * <tr><td><i>Anyone</i></td><td>Outcast</td><td>banned</td></tr> ++ * </table> ++ * </pre> ++ * ++ * @param oldAffiliation the previous affiliation of the user in the room before receiving the ++ * new presence ++ * @param newAffiliation the new affiliation of the user in the room after receiving the new ++ * presence ++ * @param isUserModification whether the received presence is about your user in the room or not ++ * @param from the occupant whose role in the room has changed ++ * (e.g. room@conference.jabber.org/nick). ++ */ ++ private void checkAffiliationModifications( ++ String oldAffiliation, ++ String newAffiliation, ++ boolean isUserModification, ++ String from) { ++ // First check for revoked affiliation and then for granted affiliations. The idea is to ++ // first fire the "revoke" events and then fire the "grant" events. ++ ++ // The user's ownership to the room was revoked ++ if ("owner".equals(oldAffiliation) && !"owner".equals(newAffiliation)) { ++ if (isUserModification) { ++ fireUserStatusListeners("ownershipRevoked", new Object[] {}); ++ } ++ else { ++ List<String> params = new ArrayList<String>(); ++ params.add(from); ++ fireParticipantStatusListeners("ownershipRevoked", params); ++ } ++ } ++ // The user's administrative privileges to the room were revoked ++ else if ("admin".equals(oldAffiliation) && !"admin".equals(newAffiliation)) { ++ if (isUserModification) { ++ fireUserStatusListeners("adminRevoked", new Object[] {}); ++ } ++ else { ++ List<String> params = new ArrayList<String>(); ++ params.add(from); ++ fireParticipantStatusListeners("adminRevoked", params); ++ } ++ } ++ // The user's membership to the room was revoked ++ else if ("member".equals(oldAffiliation) && !"member".equals(newAffiliation)) { ++ if (isUserModification) { ++ fireUserStatusListeners("membershipRevoked", new Object[] {}); ++ } ++ else { ++ List<String> params = new ArrayList<String>(); ++ params.add(from); ++ fireParticipantStatusListeners("membershipRevoked", params); ++ } ++ } ++ ++ // The user was granted ownership to the room ++ if (!"owner".equals(oldAffiliation) && "owner".equals(newAffiliation)) { ++ if (isUserModification) { ++ fireUserStatusListeners("ownershipGranted", new Object[] {}); ++ } ++ else { ++ List<String> params = new ArrayList<String>(); ++ params.add(from); ++ fireParticipantStatusListeners("ownershipGranted", params); ++ } ++ } ++ // The user was granted administrative privileges to the room ++ else if (!"admin".equals(oldAffiliation) && "admin".equals(newAffiliation)) { ++ if (isUserModification) { ++ fireUserStatusListeners("adminGranted", new Object[] {}); ++ } ++ else { ++ List<String> params = new ArrayList<String>(); ++ params.add(from); ++ fireParticipantStatusListeners("adminGranted", params); ++ } ++ } ++ // The user was granted membership to the room ++ else if (!"member".equals(oldAffiliation) && "member".equals(newAffiliation)) { ++ if (isUserModification) { ++ fireUserStatusListeners("membershipGranted", new Object[] {}); ++ } ++ else { ++ List<String> params = new ArrayList<String>(); ++ params.add(from); ++ fireParticipantStatusListeners("membershipGranted", params); ++ } ++ } ++ } ++ ++ /** ++ * Fires events according to the received presence code. ++ * ++ * @param code ++ * @param isUserModification ++ * @param mucUser ++ * @param from ++ */ ++ private void checkPresenceCode( ++ String code, ++ boolean isUserModification, ++ MUCUser mucUser, ++ String from) { ++ // Check if an occupant was kicked from the room ++ if ("307".equals(code)) { ++ // Check if this occupant was kicked ++ if (isUserModification) { ++ joined = false; ++ ++ fireUserStatusListeners( ++ "kicked", ++ new Object[] { mucUser.getItem().getActor(), mucUser.getItem().getReason()}); ++ ++ // Reset occupant information. ++ occupantsMap.clear(); ++ nickname = null; ++ userHasLeft(); ++ } ++ else { ++ List<String> params = new ArrayList<String>(); ++ params.add(from); ++ params.add(mucUser.getItem().getActor()); ++ params.add(mucUser.getItem().getReason()); ++ fireParticipantStatusListeners("kicked", params); ++ } ++ } ++ // A user was banned from the room ++ else if ("301".equals(code)) { ++ // Check if this occupant was banned ++ if (isUserModification) { ++ joined = false; ++ ++ fireUserStatusListeners( ++ "banned", ++ new Object[] { mucUser.getItem().getActor(), mucUser.getItem().getReason()}); ++ ++ // Reset occupant information. ++ occupantsMap.clear(); ++ nickname = null; ++ userHasLeft(); ++ } ++ else { ++ List<String> params = new ArrayList<String>(); ++ params.add(from); ++ params.add(mucUser.getItem().getActor()); ++ params.add(mucUser.getItem().getReason()); ++ fireParticipantStatusListeners("banned", params); ++ } ++ } ++ // A user's membership was revoked from the room ++ else if ("321".equals(code)) { ++ // Check if this occupant's membership was revoked ++ if (isUserModification) { ++ joined = false; ++ ++ fireUserStatusListeners("membershipRevoked", new Object[] {}); ++ ++ // Reset occupant information. ++ occupantsMap.clear(); ++ nickname = null; ++ userHasLeft(); ++ } ++ } ++ // A occupant has changed his nickname in the room ++ else if ("303".equals(code)) { ++ List<String> params = new ArrayList<String>(); ++ params.add(from); ++ params.add(mucUser.getItem().getNick()); ++ fireParticipantStatusListeners("nicknameChanged", params); ++ } ++ } ++ ++ protected void finalize() throws Throwable { ++ try { ++ if (connection != null) { ++ roomListenerMultiplexor.removeRoom(room); ++ // Remove all the PacketListeners added to the connection by this chat ++ for (PacketListener connectionListener : connectionListeners) { ++ connection.removePacketListener(connectionListener); ++ } ++ } ++ } ++ catch (Exception e) { ++ // Do nothing ++ } ++ super.finalize(); ++ } ++ ++ /** ++ * An InvitationsMonitor monitors a given connection to detect room invitations. Every ++ * time the InvitationsMonitor detects a new invitation it will fire the invitation listeners. ++ * ++ * @author Gaston Dombiak ++ */ ++ private static class InvitationsMonitor implements ConnectionListener { ++ // We use a WeakHashMap so that the GC can collect the monitor when the ++ // connection is no longer referenced by any object. ++ private final static Map<Connection, WeakReference<InvitationsMonitor>> monitors = ++ new WeakHashMap<Connection, WeakReference<InvitationsMonitor>>(); ++ ++ private final List<InvitationListener> invitationsListeners = ++ new ArrayList<InvitationListener>(); ++ private Connection connection; ++ private PacketFilter invitationFilter; ++ private PacketListener invitationPacketListener; ++ ++ /** ++ * Returns a new or existing InvitationsMonitor for a given connection. ++ * ++ * @param conn the connection to monitor for room invitations. ++ * @return a new or existing InvitationsMonitor for a given connection. ++ */ ++ public static InvitationsMonitor getInvitationsMonitor(Connection conn) { ++ synchronized (monitors) { ++ if (!monitors.containsKey(conn)) { ++ // We need to use a WeakReference because the monitor references the ++ // connection and this could prevent the GC from collecting the monitor ++ // when no other object references the monitor ++ monitors.put(conn, new WeakReference<InvitationsMonitor>(new InvitationsMonitor(conn))); ++ } ++ // Return the InvitationsMonitor that monitors the connection ++ return monitors.get(conn).get(); ++ } ++ } ++ ++ /** ++ * Creates a new InvitationsMonitor that will monitor invitations received ++ * on a given connection. ++ * ++ * @param connection the connection to monitor for possible room invitations ++ */ ++ private InvitationsMonitor(Connection connection) { ++ this.connection = connection; ++ } ++ ++ /** ++ * Adds a listener to invitation notifications. The listener will be fired anytime ++ * an invitation is received.<p> ++ * ++ * If this is the first monitor's listener then the monitor will be initialized in ++ * order to start listening to room invitations. ++ * ++ * @param listener an invitation listener. ++ */ ++ public void addInvitationListener(InvitationListener listener) { ++ synchronized (invitationsListeners) { ++ // If this is the first monitor's listener then initialize the listeners ++ // on the connection to detect room invitations ++ if (invitationsListeners.size() == 0) { ++ init(); ++ } ++ if (!invitationsListeners.contains(listener)) { ++ invitationsListeners.add(listener); ++ } ++ } ++ } ++ ++ /** ++ * Removes a listener to invitation notifications. The listener will be fired anytime ++ * an invitation is received.<p> ++ * ++ * If there are no more listeners to notifiy for room invitations then the monitor will ++ * be stopped. As soon as a new listener is added to the monitor, the monitor will resume ++ * monitoring the connection for new room invitations. ++ * ++ * @param listener an invitation listener. ++ */ ++ public void removeInvitationListener(InvitationListener listener) { ++ synchronized (invitationsListeners) { ++ if (invitationsListeners.contains(listener)) { ++ invitationsListeners.remove(listener); ++ } ++ // If there are no more listeners to notifiy for room invitations ++ // then proceed to cancel/release this monitor ++ if (invitationsListeners.size() == 0) { ++ cancel(); ++ } ++ } ++ } ++ ++ /** ++ * Fires invitation listeners. ++ */ ++ private void fireInvitationListeners(String room, String inviter, String reason, String password, ++ Message message) { ++ InvitationListener[] listeners; ++ synchronized (invitationsListeners) { ++ listeners = new InvitationListener[invitationsListeners.size()]; ++ invitationsListeners.toArray(listeners); ++ } ++ for (InvitationListener listener : listeners) { ++ listener.invitationReceived(connection, room, inviter, reason, password, message); ++ } ++ } ++ ++ public void connectionClosed() { ++ cancel(); ++ } ++ ++ public void connectionClosedOnError(Exception e) { ++ // ignore ++ } ++ ++ public void reconnectingIn(int seconds) { ++ // ignore ++ } ++ ++ public void reconnectionSuccessful() { ++ // ignore ++ } ++ ++ public void reconnectionFailed(Exception e) { ++ // ignore ++ } ++ ++ /** ++ * Initializes the listeners to detect received room invitations and to detect when the ++ * connection gets closed. As soon as a room invitation is received the invitations ++ * listeners will be fired. When the connection gets closed the monitor will remove ++ * his listeners on the connection. ++ */ ++ private void init() { ++ // Listens for all messages that include a MUCUser extension and fire the invitation ++ // listeners if the message includes an invitation. ++ invitationFilter = ++ new PacketExtensionFilter("x", "http://jabber.org/protocol/muc#user"); ++ invitationPacketListener = new PacketListener() { ++ public void processPacket(Packet packet) { ++ // Get the MUCUser extension ++ MUCUser mucUser = ++ (MUCUser) packet.getExtension("x", "http://jabber.org/protocol/muc#user"); ++ // Check if the MUCUser extension includes an invitation ++ if (mucUser.getInvite() != null && ++ ((Message) packet).getType() != Message.Type.error) { ++ // Fire event for invitation listeners ++ fireInvitationListeners(packet.getFrom(), mucUser.getInvite().getFrom(), ++ mucUser.getInvite().getReason(), mucUser.getPassword(), (Message) packet); ++ } ++ } ++ }; ++ connection.addPacketListener(invitationPacketListener, invitationFilter); ++ // Add a listener to detect when the connection gets closed in order to ++ // cancel/release this monitor ++ connection.addConnectionListener(this); ++ } ++ ++ /** ++ * Cancels all the listeners that this InvitationsMonitor has added to the connection. ++ */ ++ private void cancel() { ++ connection.removePacketListener(invitationPacketListener); ++ connection.removeConnectionListener(this); ++ } ++ ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/Occupant.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/Occupant.java +new file mode 100644 +index 0000000..3b199a5 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/Occupant.java +@@ -0,0 +1,121 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.muc; ++ ++import org.jivesoftware.smackx.packet.MUCAdmin; ++import org.jivesoftware.smackx.packet.MUCUser; ++import org.jivesoftware.smack.packet.Presence; ++import org.jivesoftware.smack.util.StringUtils; ++ ++/** ++ * Represents the information about an occupant in a given room. The information will always have ++ * the affiliation and role of the occupant in the room. The full JID and nickname are optional. ++ * ++ * @author Gaston Dombiak ++ */ ++public class Occupant { ++ // Fields that must have a value ++ private String affiliation; ++ private String role; ++ // Fields that may have a value ++ private String jid; ++ private String nick; ++ ++ Occupant(MUCAdmin.Item item) { ++ super(); ++ this.jid = item.getJid(); ++ this.affiliation = item.getAffiliation(); ++ this.role = item.getRole(); ++ this.nick = item.getNick(); ++ } ++ ++ Occupant(Presence presence) { ++ super(); ++ MUCUser mucUser = (MUCUser) presence.getExtension("x", ++ "http://jabber.org/protocol/muc#user"); ++ MUCUser.Item item = mucUser.getItem(); ++ this.jid = item.getJid(); ++ this.affiliation = item.getAffiliation(); ++ this.role = item.getRole(); ++ // Get the nickname from the FROM attribute of the presence ++ this.nick = StringUtils.parseResource(presence.getFrom()); ++ } ++ ++ /** ++ * Returns the full JID of the occupant. If this information was extracted from a presence and ++ * the room is semi or full-anonymous then the answer will be null. On the other hand, if this ++ * information was obtained while maintaining the voice list or the moderator list then we will ++ * always have a full JID. ++ * ++ * @return the full JID of the occupant. ++ */ ++ public String getJid() { ++ return jid; ++ } ++ ++ /** ++ * Returns the affiliation of the occupant. Possible affiliations are: "owner", "admin", ++ * "member", "outcast". This information will always be available. ++ * ++ * @return the affiliation of the occupant. ++ */ ++ public String getAffiliation() { ++ return affiliation; ++ } ++ ++ /** ++ * Returns the current role of the occupant in the room. This information will always be ++ * available. ++ * ++ * @return the current role of the occupant in the room. ++ */ ++ public String getRole() { ++ return role; ++ } ++ ++ /** ++ * Returns the current nickname of the occupant in the room. If this information was extracted ++ * from a presence then the answer will be null. ++ * ++ * @return the current nickname of the occupant in the room or null if this information was ++ * obtained from a presence. ++ */ ++ public String getNick() { ++ return nick; ++ } ++ ++ public boolean equals(Object obj) { ++ if(!(obj instanceof Occupant)) { ++ return false; ++ } ++ Occupant occupant = (Occupant)obj; ++ return jid.equals(occupant.jid); ++ } ++ ++ public int hashCode() { ++ int result; ++ result = affiliation.hashCode(); ++ result = 17 * result + role.hashCode(); ++ result = 17 * result + jid.hashCode(); ++ result = 17 * result + (nick != null ? nick.hashCode() : 0); ++ return result; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/PacketMultiplexListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/PacketMultiplexListener.java +new file mode 100644 +index 0000000..c1863c2 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/PacketMultiplexListener.java +@@ -0,0 +1,96 @@ ++/** ++ * $RCSfile$ ++ * $Revision: 2779 $ ++ * $Date: 2005-09-05 17:00:45 -0300 (Mon, 05 Sep 2005) $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx.muc; ++ ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.filter.MessageTypeFilter; ++import org.jivesoftware.smack.filter.PacketExtensionFilter; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.packet.Message; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.Presence; ++ ++/** ++ * The single <code>PacketListener</code> used by each {@link MultiUserChat} ++ * for all basic processing of presence, and message packets targeted to that chat. ++ * ++ * @author Larry Kirschner ++ */ ++class PacketMultiplexListener implements PacketListener { ++ ++ private static final PacketFilter MESSAGE_FILTER = ++ new MessageTypeFilter(Message.Type.groupchat); ++ private static final PacketFilter PRESENCE_FILTER = new PacketTypeFilter(Presence.class); ++ private static final PacketFilter SUBJECT_FILTER = new PacketFilter() { ++ public boolean accept(Packet packet) { ++ Message msg = (Message) packet; ++ return msg.getSubject() != null; ++ } ++ }; ++ private static final PacketFilter DECLINES_FILTER = ++ new PacketExtensionFilter("x", ++ "http://jabber.org/protocol/muc#user"); ++ ++ private ConnectionDetachedPacketCollector messageCollector; ++ private PacketListener presenceListener; ++ private PacketListener subjectListener; ++ private PacketListener declinesListener; ++ ++ public PacketMultiplexListener( ++ ConnectionDetachedPacketCollector messageCollector, ++ PacketListener presenceListener, ++ PacketListener subjectListener, PacketListener declinesListener) { ++ if (messageCollector == null) { ++ throw new IllegalArgumentException("MessageCollector is null"); ++ } ++ if (presenceListener == null) { ++ throw new IllegalArgumentException("Presence listener is null"); ++ } ++ if (subjectListener == null) { ++ throw new IllegalArgumentException("Subject listener is null"); ++ } ++ if (declinesListener == null) { ++ throw new IllegalArgumentException("Declines listener is null"); ++ } ++ this.messageCollector = messageCollector; ++ this.presenceListener = presenceListener; ++ this.subjectListener = subjectListener; ++ this.declinesListener = declinesListener; ++ } ++ ++ public void processPacket(Packet p) { ++ if (PRESENCE_FILTER.accept(p)) { ++ presenceListener.processPacket(p); ++ } ++ else if (MESSAGE_FILTER.accept(p)) { ++ messageCollector.processPacket(p); ++ ++ if (SUBJECT_FILTER.accept(p)) { ++ subjectListener.processPacket(p); ++ } ++ } ++ else if (DECLINES_FILTER.accept(p)) { ++ declinesListener.processPacket(p); ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/ParticipantStatusListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/ParticipantStatusListener.java +new file mode 100644 +index 0000000..c3e248d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/ParticipantStatusListener.java +@@ -0,0 +1,179 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.muc; ++ ++/** ++ * A listener that is fired anytime a participant's status in a room is changed, such as the ++ * user being kicked, banned, or granted admin permissions. ++ * ++ * @author Gaston Dombiak ++ */ ++public interface ParticipantStatusListener { ++ ++ /** ++ * Called when a new room occupant has joined the room. Note: Take in consideration that when ++ * you join a room you will receive the list of current occupants in the room. This message will ++ * be sent for each occupant. ++ * ++ * @param participant the participant that has just joined the room ++ * (e.g. room@conference.jabber.org/nick). ++ */ ++ public abstract void joined(String participant); ++ ++ /** ++ * Called when a room occupant has left the room on its own. This means that the occupant was ++ * neither kicked nor banned from the room. ++ * ++ * @param participant the participant that has left the room on its own. ++ * (e.g. room@conference.jabber.org/nick). ++ */ ++ public abstract void left(String participant); ++ ++ /** ++ * Called when a room participant has been kicked from the room. This means that the kicked ++ * participant is no longer participating in the room. ++ * ++ * @param participant the participant that was kicked from the room ++ * (e.g. room@conference.jabber.org/nick). ++ * @param actor the moderator that kicked the occupant from the room (e.g. user@host.org). ++ * @param reason the reason provided by the actor to kick the occupant from the room. ++ */ ++ public abstract void kicked(String participant, String actor, String reason); ++ ++ /** ++ * Called when a moderator grants voice to a visitor. This means that the visitor ++ * can now participate in the moderated room sending messages to all occupants. ++ * ++ * @param participant the participant that was granted voice in the room ++ * (e.g. room@conference.jabber.org/nick). ++ */ ++ public abstract void voiceGranted(String participant); ++ ++ /** ++ * Called when a moderator revokes voice from a participant. This means that the participant ++ * in the room was able to speak and now is a visitor that can't send messages to the room ++ * occupants. ++ * ++ * @param participant the participant that was revoked voice from the room ++ * (e.g. room@conference.jabber.org/nick). ++ */ ++ public abstract void voiceRevoked(String participant); ++ ++ /** ++ * Called when an administrator or owner banned a participant from the room. This means that ++ * banned participant will no longer be able to join the room unless the ban has been removed. ++ * ++ * @param participant the participant that was banned from the room ++ * (e.g. room@conference.jabber.org/nick). ++ * @param actor the administrator that banned the occupant (e.g. user@host.org). ++ * @param reason the reason provided by the administrator to ban the occupant. ++ */ ++ public abstract void banned(String participant, String actor, String reason); ++ ++ /** ++ * Called when an administrator grants a user membership to the room. This means that the user ++ * will be able to join the members-only room. ++ * ++ * @param participant the participant that was granted membership in the room ++ * (e.g. room@conference.jabber.org/nick). ++ */ ++ public abstract void membershipGranted(String participant); ++ ++ /** ++ * Called when an administrator revokes a user membership to the room. This means that the ++ * user will not be able to join the members-only room. ++ * ++ * @param participant the participant that was revoked membership from the room ++ * (e.g. room@conference.jabber.org/nick). ++ */ ++ public abstract void membershipRevoked(String participant); ++ ++ /** ++ * Called when an administrator grants moderator privileges to a user. This means that the user ++ * will be able to kick users, grant and revoke voice, invite other users, modify room's ++ * subject plus all the partcipants privileges. ++ * ++ * @param participant the participant that was granted moderator privileges in the room ++ * (e.g. room@conference.jabber.org/nick). ++ */ ++ public abstract void moderatorGranted(String participant); ++ ++ /** ++ * Called when an administrator revokes moderator privileges from a user. This means that the ++ * user will no longer be able to kick users, grant and revoke voice, invite other users, ++ * modify room's subject plus all the partcipants privileges. ++ * ++ * @param participant the participant that was revoked moderator privileges in the room ++ * (e.g. room@conference.jabber.org/nick). ++ */ ++ public abstract void moderatorRevoked(String participant); ++ ++ /** ++ * Called when an owner grants a user ownership on the room. This means that the user ++ * will be able to change defining room features as well as perform all administrative ++ * functions. ++ * ++ * @param participant the participant that was granted ownership on the room ++ * (e.g. room@conference.jabber.org/nick). ++ */ ++ public abstract void ownershipGranted(String participant); ++ ++ /** ++ * Called when an owner revokes a user ownership on the room. This means that the user ++ * will no longer be able to change defining room features as well as perform all ++ * administrative functions. ++ * ++ * @param participant the participant that was revoked ownership on the room ++ * (e.g. room@conference.jabber.org/nick). ++ */ ++ public abstract void ownershipRevoked(String participant); ++ ++ /** ++ * Called when an owner grants administrator privileges to a user. This means that the user ++ * will be able to perform administrative functions such as banning users and edit moderator ++ * list. ++ * ++ * @param participant the participant that was granted administrator privileges ++ * (e.g. room@conference.jabber.org/nick). ++ */ ++ public abstract void adminGranted(String participant); ++ ++ /** ++ * Called when an owner revokes administrator privileges from a user. This means that the user ++ * will no longer be able to perform administrative functions such as banning users and edit ++ * moderator list. ++ * ++ * @param participant the participant that was revoked administrator privileges ++ * (e.g. room@conference.jabber.org/nick). ++ */ ++ public abstract void adminRevoked(String participant); ++ ++ /** ++ * Called when a participant changed his/her nickname in the room. The new participant's ++ * nickname will be informed with the next available presence. ++ * ++ * @param participant the participant that was revoked administrator privileges ++ * (e.g. room@conference.jabber.org/nick). ++ * @param newNickname the new nickname that the participant decided to use. ++ */ ++ public abstract void nicknameChanged(String participant, String newNickname); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/RoomInfo.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/RoomInfo.java +new file mode 100644 +index 0000000..f97f544 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/RoomInfo.java +@@ -0,0 +1,190 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.muc; ++ ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.FormField; ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++ ++import java.util.Iterator; ++ ++/** ++ * Represents the room information that was discovered using Service Discovery. It's possible to ++ * obtain information about a room before joining the room but only for rooms that are public (i.e. ++ * rooms that may be discovered). ++ * ++ * @author Gaston Dombiak ++ */ ++public class RoomInfo { ++ ++ /** ++ * JID of the room. The node of the JID is commonly used as the ID of the room or name. ++ */ ++ private String room; ++ /** ++ * Description of the room. ++ */ ++ private String description = ""; ++ /** ++ * Last known subject of the room. ++ */ ++ private String subject = ""; ++ /** ++ * Current number of occupants in the room. ++ */ ++ private int occupantsCount = -1; ++ /** ++ * A room is considered members-only if an invitation is required in order to enter the room. ++ * Any user that is not a member of the room won't be able to join the room unless the user ++ * decides to register with the room (thus becoming a member). ++ */ ++ private boolean membersOnly; ++ /** ++ * Moderated rooms enable only participants to speak. Users that join the room and aren't ++ * participants can't speak (they are just visitors). ++ */ ++ private boolean moderated; ++ /** ++ * Every presence packet can include the JID of every occupant unless the owner deactives this ++ * configuration. ++ */ ++ private boolean nonanonymous; ++ /** ++ * Indicates if users must supply a password to join the room. ++ */ ++ private boolean passwordProtected; ++ /** ++ * Persistent rooms are saved to the database to make sure that rooms configurations can be ++ * restored in case the server goes down. ++ */ ++ private boolean persistent; ++ ++ RoomInfo(DiscoverInfo info) { ++ super(); ++ this.room = info.getFrom(); ++ // Get the information based on the discovered features ++ this.membersOnly = info.containsFeature("muc_membersonly"); ++ this.moderated = info.containsFeature("muc_moderated"); ++ this.nonanonymous = info.containsFeature("muc_nonanonymous"); ++ this.passwordProtected = info.containsFeature("muc_passwordprotected"); ++ this.persistent = info.containsFeature("muc_persistent"); ++ // Get the information based on the discovered extended information ++ Form form = Form.getFormFrom(info); ++ if (form != null) { ++ FormField descField = form.getField("muc#roominfo_description"); ++ this.description = ( descField == null || !(descField.getValues().hasNext()) )? "" : descField.getValues().next(); ++ ++ FormField subjField = form.getField("muc#roominfo_subject"); ++ this.subject = ( subjField == null || !(subjField.getValues().hasNext()) ) ? "" : subjField.getValues().next(); ++ ++ FormField occCountField = form.getField("muc#roominfo_occupants"); ++ this.occupantsCount = occCountField == null ? -1 : Integer.parseInt(occCountField.getValues() ++ .next()); ++ } ++ } ++ ++ /** ++ * Returns the JID of the room whose information was discovered. ++ * ++ * @return the JID of the room whose information was discovered. ++ */ ++ public String getRoom() { ++ return room; ++ } ++ ++ /** ++ * Returns the discovered description of the room. ++ * ++ * @return the discovered description of the room. ++ */ ++ public String getDescription() { ++ return description; ++ } ++ ++ /** ++ * Returns the discovered subject of the room. The subject may be empty if the room does not ++ * have a subject. ++ * ++ * @return the discovered subject of the room. ++ */ ++ public String getSubject() { ++ return subject; ++ } ++ ++ /** ++ * Returns the discovered number of occupants that are currently in the room. If this ++ * information was not discovered (i.e. the server didn't send it) then a value of -1 will be ++ * returned. ++ * ++ * @return the number of occupants that are currently in the room or -1 if that information was ++ * not provided by the server. ++ */ ++ public int getOccupantsCount() { ++ return occupantsCount; ++ } ++ ++ /** ++ * Returns true if the room has restricted the access so that only members may enter the room. ++ * ++ * @return true if the room has restricted the access so that only members may enter the room. ++ */ ++ public boolean isMembersOnly() { ++ return membersOnly; ++ } ++ ++ /** ++ * Returns true if the room enabled only participants to speak. Occupants with a role of ++ * visitor won't be able to speak in the room. ++ * ++ * @return true if the room enabled only participants to speak. ++ */ ++ public boolean isModerated() { ++ return moderated; ++ } ++ ++ /** ++ * Returns true if presence packets will include the JID of every occupant. ++ * ++ * @return true if presence packets will include the JID of every occupant. ++ */ ++ public boolean isNonanonymous() { ++ return nonanonymous; ++ } ++ ++ /** ++ * Returns true if users musy provide a valid password in order to join the room. ++ * ++ * @return true if users musy provide a valid password in order to join the room. ++ */ ++ public boolean isPasswordProtected() { ++ return passwordProtected; ++ } ++ ++ /** ++ * Returns true if the room will persist after the last occupant have left the room. ++ * ++ * @return true if the room will persist after the last occupant have left the room. ++ */ ++ public boolean isPersistent() { ++ return persistent; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/RoomListenerMultiplexor.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/RoomListenerMultiplexor.java +new file mode 100644 +index 0000000..6f8bf05 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/RoomListenerMultiplexor.java +@@ -0,0 +1,227 @@ ++/** ++ * $RCSfile$ ++ * $Revision: 2779 $ ++ * $Date: 2005-09-05 17:00:45 -0300 (Mon, 05 Sep 2005) $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx.muc; ++ ++import org.jivesoftware.smack.ConnectionListener; ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.util.StringUtils; ++ ++import java.lang.ref.WeakReference; ++import java.util.Map; ++import java.util.WeakHashMap; ++import java.util.concurrent.ConcurrentHashMap; ++ ++/** ++ * A <code>RoomListenerMultiplexor</code> multiplexes incoming packets on ++ * a <code>Connection</code> using a single listener/filter pair. ++ * A single <code>RoomListenerMultiplexor</code> is created for each ++ * {@link org.jivesoftware.smack.Connection} that has joined MUC rooms ++ * within its session. ++ * ++ * @author Larry Kirschner ++ */ ++class RoomListenerMultiplexor implements ConnectionListener { ++ ++ // We use a WeakHashMap so that the GC can collect the monitor when the ++ // connection is no longer referenced by any object. ++ private static final Map<Connection, WeakReference<RoomListenerMultiplexor>> monitors = ++ new WeakHashMap<Connection, WeakReference<RoomListenerMultiplexor>>(); ++ ++ private Connection connection; ++ private RoomMultiplexFilter filter; ++ private RoomMultiplexListener listener; ++ ++ /** ++ * Returns a new or existing RoomListenerMultiplexor for a given connection. ++ * ++ * @param conn the connection to monitor for room invitations. ++ * @return a new or existing RoomListenerMultiplexor for a given connection. ++ */ ++ public static RoomListenerMultiplexor getRoomMultiplexor(Connection conn) { ++ synchronized (monitors) { ++ if (!monitors.containsKey(conn) || monitors.get(conn).get() == null) { ++ RoomListenerMultiplexor rm = new RoomListenerMultiplexor(conn, new RoomMultiplexFilter(), ++ new RoomMultiplexListener()); ++ ++ rm.init(); ++ ++ // We need to use a WeakReference because the monitor references the ++ // connection and this could prevent the GC from collecting the monitor ++ // when no other object references the monitor ++ monitors.put(conn, new WeakReference<RoomListenerMultiplexor>(rm)); ++ } ++ // Return the InvitationsMonitor that monitors the connection ++ return monitors.get(conn).get(); ++ } ++ } ++ ++ /** ++ * All access should be through ++ * the static method {@link #getRoomMultiplexor(Connection)}. ++ */ ++ private RoomListenerMultiplexor(Connection connection, RoomMultiplexFilter filter, ++ RoomMultiplexListener listener) { ++ if (connection == null) { ++ throw new IllegalArgumentException("Connection is null"); ++ } ++ if (filter == null) { ++ throw new IllegalArgumentException("Filter is null"); ++ } ++ if (listener == null) { ++ throw new IllegalArgumentException("Listener is null"); ++ } ++ this.connection = connection; ++ this.filter = filter; ++ this.listener = listener; ++ } ++ ++ public void addRoom(String address, PacketMultiplexListener roomListener) { ++ filter.addRoom(address); ++ listener.addRoom(address, roomListener); ++ } ++ ++ public void connectionClosed() { ++ cancel(); ++ } ++ ++ public void connectionClosedOnError(Exception e) { ++ cancel(); ++ } ++ ++ public void reconnectingIn(int seconds) { ++ // ignore ++ } ++ ++ public void reconnectionSuccessful() { ++ // ignore ++ } ++ ++ public void reconnectionFailed(Exception e) { ++ // ignore ++ } ++ ++ /** ++ * Initializes the listeners to detect received room invitations and to detect when the ++ * connection gets closed. As soon as a room invitation is received the invitations ++ * listeners will be fired. When the connection gets closed the monitor will remove ++ * his listeners on the connection. ++ */ ++ public void init() { ++ connection.addConnectionListener(this); ++ connection.addPacketListener(listener, filter); ++ } ++ ++ public void removeRoom(String address) { ++ filter.removeRoom(address); ++ listener.removeRoom(address); ++ } ++ ++ /** ++ * Cancels all the listeners that this InvitationsMonitor has added to the connection. ++ */ ++ private void cancel() { ++ connection.removeConnectionListener(this); ++ connection.removePacketListener(listener); ++ } ++ ++ /** ++ * The single <code>Connection</code>-level <code>PacketFilter</code> used by a {@link RoomListenerMultiplexor} ++ * for all muc chat rooms on an <code>Connection</code>. ++ * Each time a muc chat room is added to/removed from an ++ * <code>Connection</code> the address for that chat room ++ * is added to/removed from that <code>Connection</code>'s ++ * <code>RoomMultiplexFilter</code>. ++ */ ++ private static class RoomMultiplexFilter implements PacketFilter { ++ ++ private Map<String, String> roomAddressTable = new ConcurrentHashMap<String, String>(); ++ ++ public boolean accept(Packet p) { ++ String from = p.getFrom(); ++ if (from == null) { ++ return false; ++ } ++ return roomAddressTable.containsKey(StringUtils.parseBareAddress(from).toLowerCase()); ++ } ++ ++ public void addRoom(String address) { ++ if (address == null) { ++ return; ++ } ++ roomAddressTable.put(address.toLowerCase(), address); ++ } ++ ++ public void removeRoom(String address) { ++ if (address == null) { ++ return; ++ } ++ roomAddressTable.remove(address.toLowerCase()); ++ } ++ } ++ ++ /** ++ * The single <code>Connection</code>-level <code>PacketListener</code> ++ * used by a {@link RoomListenerMultiplexor} ++ * for all muc chat rooms on an <code>Connection</code>. ++ * Each time a muc chat room is added to/removed from an ++ * <code>Connection</code> the address and listener for that chat room ++ * are added to/removed from that <code>Connection</code>'s ++ * <code>RoomMultiplexListener</code>. ++ * ++ * @author Larry Kirschner ++ */ ++ private static class RoomMultiplexListener implements PacketListener { ++ ++ private Map<String, PacketMultiplexListener> roomListenersByAddress = ++ new ConcurrentHashMap<String, PacketMultiplexListener>(); ++ ++ public void processPacket(Packet p) { ++ String from = p.getFrom(); ++ if (from == null) { ++ return; ++ } ++ ++ PacketMultiplexListener listener = ++ roomListenersByAddress.get(StringUtils.parseBareAddress(from).toLowerCase()); ++ ++ if (listener != null) { ++ listener.processPacket(p); ++ } ++ } ++ ++ public void addRoom(String address, PacketMultiplexListener listener) { ++ if (address == null) { ++ return; ++ } ++ roomListenersByAddress.put(address.toLowerCase(), listener); ++ } ++ ++ public void removeRoom(String address) { ++ if (address == null) { ++ return; ++ } ++ roomListenersByAddress.remove(address.toLowerCase()); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/SubjectUpdatedListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/SubjectUpdatedListener.java +new file mode 100644 +index 0000000..3a2deab +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/SubjectUpdatedListener.java +@@ -0,0 +1,38 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.muc; ++ ++/** ++ * A listener that is fired anytime a MUC room changes its subject. ++ * ++ * @author Gaston Dombiak ++ */ ++public interface SubjectUpdatedListener { ++ ++ /** ++ * Called when a MUC room has changed its subject. ++ * ++ * @param subject the new room's subject. ++ * @param from the user that changed the room's subject (e.g. room@conference.jabber.org/nick). ++ */ ++ public abstract void subjectUpdated(String subject, String from); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/UserStatusListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/UserStatusListener.java +new file mode 100644 +index 0000000..27f0f58 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/UserStatusListener.java +@@ -0,0 +1,127 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.muc; ++ ++/** ++ * A listener that is fired anytime your participant's status in a room is changed, such as the ++ * user being kicked, banned, or granted admin permissions. ++ * ++ * @author Gaston Dombiak ++ */ ++public interface UserStatusListener { ++ ++ /** ++ * Called when a moderator kicked your user from the room. This means that you are no longer ++ * participanting in the room. ++ * ++ * @param actor the moderator that kicked your user from the room (e.g. user@host.org). ++ * @param reason the reason provided by the actor to kick you from the room. ++ */ ++ public abstract void kicked(String actor, String reason); ++ ++ /** ++ * Called when a moderator grants voice to your user. This means that you were a visitor in ++ * the moderated room before and now you can participate in the room by sending messages to ++ * all occupants. ++ * ++ */ ++ public abstract void voiceGranted(); ++ ++ /** ++ * Called when a moderator revokes voice from your user. This means that you were a ++ * participant in the room able to speak and now you are a visitor that can't send ++ * messages to the room occupants. ++ * ++ */ ++ public abstract void voiceRevoked(); ++ ++ /** ++ * Called when an administrator or owner banned your user from the room. This means that you ++ * will no longer be able to join the room unless the ban has been removed. ++ * ++ * @param actor the administrator that banned your user (e.g. user@host.org). ++ * @param reason the reason provided by the administrator to banned you. ++ */ ++ public abstract void banned(String actor, String reason); ++ ++ /** ++ * Called when an administrator grants your user membership to the room. This means that you ++ * will be able to join the members-only room. ++ * ++ */ ++ public abstract void membershipGranted(); ++ ++ /** ++ * Called when an administrator revokes your user membership to the room. This means that you ++ * will not be able to join the members-only room. ++ * ++ */ ++ public abstract void membershipRevoked(); ++ ++ /** ++ * Called when an administrator grants moderator privileges to your user. This means that you ++ * will be able to kick users, grant and revoke voice, invite other users, modify room's ++ * subject plus all the partcipants privileges. ++ * ++ */ ++ public abstract void moderatorGranted(); ++ ++ /** ++ * Called when an administrator revokes moderator privileges from your user. This means that ++ * you will no longer be able to kick users, grant and revoke voice, invite other users, ++ * modify room's subject plus all the partcipants privileges. ++ * ++ */ ++ public abstract void moderatorRevoked(); ++ ++ /** ++ * Called when an owner grants to your user ownership on the room. This means that you ++ * will be able to change defining room features as well as perform all administrative ++ * functions. ++ * ++ */ ++ public abstract void ownershipGranted(); ++ ++ /** ++ * Called when an owner revokes from your user ownership on the room. This means that you ++ * will no longer be able to change defining room features as well as perform all ++ * administrative functions. ++ * ++ */ ++ public abstract void ownershipRevoked(); ++ ++ /** ++ * Called when an owner grants administrator privileges to your user. This means that you ++ * will be able to perform administrative functions such as banning users and edit moderator ++ * list. ++ * ++ */ ++ public abstract void adminGranted(); ++ ++ /** ++ * Called when an owner revokes administrator privileges from your user. This means that you ++ * will no longer be able to perform administrative functions such as banning users and edit ++ * moderator list. ++ * ++ */ ++ public abstract void adminRevoked(); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/package.html +new file mode 100644 +index 0000000..dcfaeaa +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/package.html +@@ -0,0 +1 @@ ++<body>Classes and Interfaces that implement Multi-User Chat (MUC).</body> +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smackx/package.html +new file mode 100644 +index 0000000..d574a2a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/package.html +@@ -0,0 +1 @@ ++<body>Smack extensions API.</body> +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/AdHocCommandData.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/AdHocCommandData.java +new file mode 100755 +index 0000000..d0b49af +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/AdHocCommandData.java +@@ -0,0 +1,279 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2005-2008 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.commands.AdHocCommand; ++import org.jivesoftware.smackx.commands.AdHocCommand.Action; ++import org.jivesoftware.smackx.commands.AdHocCommand.SpecificErrorCondition; ++import org.jivesoftware.smackx.commands.AdHocCommandNote; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++/** ++ * Represents the state and the request of the execution of an adhoc command. ++ * ++ * @author Gabriel Guardincerri ++ */ ++public class AdHocCommandData extends IQ { ++ ++ /* JID of the command host */ ++ private String id; ++ ++ /* Command name */ ++ private String name; ++ ++ /* Command identifier */ ++ private String node; ++ ++ /* Unique ID of the execution */ ++ private String sessionID; ++ ++ private List<AdHocCommandNote> notes = new ArrayList<AdHocCommandNote>(); ++ ++ private DataForm form; ++ ++ /* Action request to be executed */ ++ private AdHocCommand.Action action; ++ ++ /* Current execution status */ ++ private AdHocCommand.Status status; ++ ++ private ArrayList<AdHocCommand.Action> actions = new ArrayList<AdHocCommand.Action>(); ++ ++ private AdHocCommand.Action executeAction; ++ ++ private String lang; ++ ++ public AdHocCommandData() { ++ } ++ ++ @Override ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<command xmlns=\"http://jabber.org/protocol/commands\""); ++ buf.append(" node=\"").append(node).append("\""); ++ if (sessionID != null) { ++ if (!sessionID.equals("")) { ++ buf.append(" sessionid=\"").append(sessionID).append("\""); ++ } ++ } ++ if (status != null) { ++ buf.append(" status=\"").append(status).append("\""); ++ } ++ if (action != null) { ++ buf.append(" action=\"").append(action).append("\""); ++ } ++ ++ if (lang != null) { ++ if (!lang.equals("")) { ++ buf.append(" lang=\"").append(lang).append("\""); ++ } ++ } ++ buf.append(">"); ++ ++ if (getType() == Type.RESULT) { ++ buf.append("<actions"); ++ ++ if (executeAction != null) { ++ buf.append(" execute=\"").append(executeAction).append("\""); ++ } ++ if (actions.size() == 0) { ++ buf.append("/>"); ++ } else { ++ buf.append(">"); ++ ++ for (AdHocCommand.Action action : actions) { ++ buf.append("<").append(action).append("/>"); ++ } ++ buf.append("</actions>"); ++ } ++ } ++ ++ if (form != null) { ++ buf.append(form.toXML()); ++ } ++ ++ for (AdHocCommandNote note : notes) { ++ buf.append("<note type=\"").append(note.getType().toString()).append("\">"); ++ buf.append(note.getValue()); ++ buf.append("</note>"); ++ } ++ ++ // TODO ERRORS ++// if (getError() != null) { ++// buf.append(getError().toXML()); ++// } ++ ++ buf.append("</command>"); ++ return buf.toString(); ++ } ++ ++ /** ++ * Returns the JID of the command host. ++ * ++ * @return the JID of the command host. ++ */ ++ public String getId() { ++ return id; ++ } ++ ++ public void setId(String id) { ++ this.id = id; ++ } ++ ++ /** ++ * Returns the human name of the command ++ * ++ * @return the name of the command. ++ */ ++ public String getName() { ++ return name; ++ } ++ ++ public void setName(String name) { ++ this.name = name; ++ } ++ ++ /** ++ * Returns the identifier of the command ++ * ++ * @return the node. ++ */ ++ public String getNode() { ++ return node; ++ } ++ ++ public void setNode(String node) { ++ this.node = node; ++ } ++ ++ /** ++ * Returns the list of notes that the command has. ++ * ++ * @return the notes. ++ */ ++ public List<AdHocCommandNote> getNotes() { ++ return notes; ++ } ++ ++ public void addNote(AdHocCommandNote note) { ++ this.notes.add(note); ++ } ++ ++ public void remveNote(AdHocCommandNote note) { ++ this.notes.remove(note); ++ } ++ ++ /** ++ * Returns the form of the command. ++ * ++ * @return the data form associated with the command. ++ */ ++ public DataForm getForm() { ++ return form; ++ } ++ ++ public void setForm(DataForm form) { ++ this.form = form; ++ } ++ ++ /** ++ * Returns the action to execute. The action is set only on a request. ++ * ++ * @return the action to execute. ++ */ ++ public AdHocCommand.Action getAction() { ++ return action; ++ } ++ ++ public void setAction(AdHocCommand.Action action) { ++ this.action = action; ++ } ++ ++ /** ++ * Returns the status of the execution. ++ * ++ * @return the status. ++ */ ++ public AdHocCommand.Status getStatus() { ++ return status; ++ } ++ ++ public void setStatus(AdHocCommand.Status status) { ++ this.status = status; ++ } ++ ++ public List<Action> getActions() { ++ return actions; ++ } ++ ++ public void addAction(Action action) { ++ actions.add(action); ++ } ++ ++ public void setExecuteAction(Action executeAction) { ++ this.executeAction = executeAction; ++ } ++ ++ public Action getExecuteAction() { ++ return executeAction; ++ } ++ ++ public void setSessionID(String sessionID) { ++ this.sessionID = sessionID; ++ } ++ ++ public String getSessionID() { ++ return sessionID; ++ } ++ ++ public static class SpecificError implements PacketExtension { ++ ++ public static final String namespace = "http://jabber.org/protocol/commands"; ++ ++ public SpecificErrorCondition condition; ++ ++ public SpecificError(SpecificErrorCondition condition) { ++ this.condition = condition; ++ } ++ ++ public String getElementName() { ++ return condition.toString(); ++ } ++ public String getNamespace() { ++ return namespace; ++ } ++ ++ public SpecificErrorCondition getCondition() { ++ return condition; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()); ++ buf.append(" xmlns=\"").append(getNamespace()).append("\"/>"); ++ return buf.toString(); ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/AttentionExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/AttentionExtension.java +new file mode 100644 +index 0000000..d86fa41 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/AttentionExtension.java +@@ -0,0 +1,100 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2010 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * A PacketExtension that implements XEP-0224: Attention ++ * ++ * This extension is expected to be added to message stanzas of type 'headline.' ++ * Please refer to the XEP for more implementation guidelines. ++ * ++ * @author Guus der Kinderen, guus.der.kinderen@gmail.com ++ * @see <a ++ * href="http://xmpp.org/extensions/xep-0224.html">XEP-0224: Attention</a> ++ */ ++public class AttentionExtension implements PacketExtension { ++ ++ /** ++ * The XML element name of an 'attention' extension. ++ */ ++ public static final String ELEMENT_NAME = "attention"; ++ ++ /** ++ * The namespace that qualifies the XML element of an 'attention' extension. ++ */ ++ public static final String NAMESPACE = "urn:xmpp:attention:0"; ++ ++ /* ++ * (non-Javadoc) ++ * ++ * @see org.jivesoftware.smack.packet.PacketExtension#getElementName() ++ */ ++ public String getElementName() { ++ return ELEMENT_NAME; ++ } ++ ++ /* ++ * (non-Javadoc) ++ * ++ * @see org.jivesoftware.smack.packet.PacketExtension#getNamespace() ++ */ ++ public String getNamespace() { ++ return NAMESPACE; ++ } ++ ++ /* ++ * (non-Javadoc) ++ * ++ * @see org.jivesoftware.smack.packet.PacketExtension#toXML() ++ */ ++ public String toXML() { ++ final StringBuilder sb = new StringBuilder(); ++ sb.append("<").append(getElementName()).append(" xmlns=\"").append( ++ getNamespace()).append("\"/>"); ++ return sb.toString(); ++ } ++ ++ /** ++ * A {@link PacketExtensionProvider} for the {@link AttentionExtension}. As ++ * Attention elements have no state/information other than the element name ++ * and namespace, this implementation simply returns new instances of ++ * {@link AttentionExtension}. ++ * ++ * @author Guus der Kinderen, guus.der.kinderen@gmail.com ++s */ ++ public static class Provider implements PacketExtensionProvider { ++ ++ /* ++ * (non-Javadoc) ++ * ++ * @see ++ * org.jivesoftware.smack.provider.PacketExtensionProvider#parseExtension ++ * (org.xmlpull.v1.XmlPullParser) ++ */ ++ public PacketExtension parseExtension(XmlPullParser arg0) ++ throws Exception { ++ return new AttentionExtension(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/CapsExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/CapsExtension.java +new file mode 100644 +index 0000000..a7fc144 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/CapsExtension.java +@@ -0,0 +1,83 @@ ++/* ++ * Copyright 2009 Jonas Ã…dahl. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++public class CapsExtension implements PacketExtension { ++ ++ private String node, version, hash; ++ public static final String XMLNS = "http://jabber.org/protocol/caps"; ++ public static final String NODE_NAME = "c"; ++ ++ public CapsExtension() { ++ } ++ ++ public CapsExtension(String node, String version, String hash) { ++ this.node = node; ++ this.version = version; ++ this.hash = hash; ++ } ++ ++ public String getElementName() { ++ return NODE_NAME; ++ } ++ ++ public String getNamespace() { ++ return XMLNS; ++ } ++ ++ public String getNode() { ++ return node; ++ } ++ ++ public void setNode(String node) { ++ this.node = node; ++ } ++ ++ public String getVersion() { ++ return version; ++ } ++ ++ public void setVersion(String version) { ++ this.version = version; ++ } ++ ++ public String getHash() { ++ return hash; ++ } ++ ++ public void setHash(String hash) { ++ this.hash = hash; ++ } ++ ++ /* ++ * <c xmlns='http://jabber.org/protocol/caps' ++ * hash='sha-1' ++ * node='http://code.google.com/p/exodus' ++ * ver='QgayPKawpkPSDYmwT/WM94uAlu0='/> ++ * ++ */ ++ public String toXML() { ++ String xml = "<c xmlns='" + XMLNS + "' " + ++ "hash='" + hash + "' " + ++ "node='" + node + "' " + ++ "ver='" + version + "'/>"; ++ ++ return xml; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/ChatStateExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/ChatStateExtension.java +new file mode 100644 +index 0000000..ecc6acc +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/ChatStateExtension.java +@@ -0,0 +1,73 @@ ++/** ++ * $RCSfile$ ++ * $Revision: 2407 $ ++ * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smackx.ChatState; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * Represents a chat state which is an extension to message packets which is used to indicate ++ * the current status of a chat participant. ++ * ++ * @author Alexander Wenckus ++ * @see org.jivesoftware.smackx.ChatState ++ */ ++public class ChatStateExtension implements PacketExtension { ++ ++ private ChatState state; ++ ++ /** ++ * Default constructor. The argument provided is the state that the extension will represent. ++ * ++ * @param state the state that the extension represents. ++ */ ++ public ChatStateExtension(ChatState state) { ++ this.state = state; ++ } ++ ++ public String getElementName() { ++ return state.name(); ++ } ++ ++ public String getNamespace() { ++ return "http://jabber.org/protocol/chatstates"; ++ } ++ ++ public String toXML() { ++ return "<" + getElementName() + " xmlns=\"" + getNamespace() + "\" />"; ++ } ++ ++ public static class Provider implements PacketExtensionProvider { ++ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ ChatState state; ++ try { ++ state = ChatState.valueOf(parser.getName()); ++ } ++ catch (Exception ex) { ++ state = ChatState.active; ++ } ++ return new ChatStateExtension(state); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DataForm.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DataForm.java +new file mode 100644 +index 0000000..c0c2630 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DataForm.java +@@ -0,0 +1,296 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.FormField; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++ ++/** ++ * Represents a form that could be use for gathering data as well as for reporting data ++ * returned from a search. ++ * ++ * @author Gaston Dombiak ++ */ ++public class DataForm implements PacketExtension { ++ ++ private String type; ++ private String title; ++ private List<String> instructions = new ArrayList<String>(); ++ private ReportedData reportedData; ++ private final List<Item> items = new ArrayList<Item>(); ++ private final List<FormField> fields = new ArrayList<FormField>(); ++ ++ public DataForm(String type) { ++ this.type = type; ++ } ++ ++ /** ++ * Returns the meaning of the data within the context. The data could be part of a form ++ * to fill out, a form submission or data results.<p> ++ * ++ * Possible form types are: ++ * <ul> ++ * <li>form -> This packet contains a form to fill out. Display it to the user (if your ++ * program can).</li> ++ * <li>submit -> The form is filled out, and this is the data that is being returned from ++ * the form.</li> ++ * <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li> ++ * <li>result -> Data results being returned from a search, or some other query.</li> ++ * </ul> ++ * ++ * @return the form's type. ++ */ ++ public String getType() { ++ return type; ++ } ++ ++ /** ++ * Returns the description of the data. It is similar to the title on a web page or an X ++ * window. You can put a <title/> on either a form to fill out, or a set of data results. ++ * ++ * @return description of the data. ++ */ ++ public String getTitle() { ++ return title; ++ } ++ ++ /** ++ * Returns an Iterator for the list of instructions that explain how to fill out the form and ++ * what the form is about. The dataform could include multiple instructions since each ++ * instruction could not contain newlines characters. Join the instructions together in order ++ * to show them to the user. ++ * ++ * @return an Iterator for the list of instructions that explain how to fill out the form. ++ */ ++ public Iterator<String> getInstructions() { ++ synchronized (instructions) { ++ return Collections.unmodifiableList(new ArrayList<String>(instructions)).iterator(); ++ } ++ } ++ ++ /** ++ * Returns the fields that will be returned from a search. ++ * ++ * @return fields that will be returned from a search. ++ */ ++ public ReportedData getReportedData() { ++ return reportedData; ++ } ++ ++ /** ++ * Returns an Iterator for the items returned from a search. ++ * ++ * @return an Iterator for the items returned from a search. ++ */ ++ public Iterator<Item> getItems() { ++ synchronized (items) { ++ return Collections.unmodifiableList(new ArrayList<Item>(items)).iterator(); ++ } ++ } ++ ++ /** ++ * Returns an Iterator for the fields that are part of the form. ++ * ++ * @return an Iterator for the fields that are part of the form. ++ */ ++ public Iterator<FormField> getFields() { ++ synchronized (fields) { ++ return Collections.unmodifiableList(new ArrayList<FormField>(fields)).iterator(); ++ } ++ } ++ ++ public String getElementName() { ++ return "x"; ++ } ++ ++ public String getNamespace() { ++ return "jabber:x:data"; ++ } ++ ++ /** ++ * Sets the description of the data. It is similar to the title on a web page or an X window. ++ * You can put a <title/> on either a form to fill out, or a set of data results. ++ * ++ * @param title description of the data. ++ */ ++ public void setTitle(String title) { ++ this.title = title; ++ } ++ ++ /** ++ * Sets the list of instructions that explain how to fill out the form and what the form is ++ * about. The dataform could include multiple instructions since each instruction could not ++ * contain newlines characters. ++ * ++ * @param instructions list of instructions that explain how to fill out the form. ++ */ ++ public void setInstructions(List<String> instructions) { ++ this.instructions = instructions; ++ } ++ ++ /** ++ * Sets the fields that will be returned from a search. ++ * ++ * @param reportedData the fields that will be returned from a search. ++ */ ++ public void setReportedData(ReportedData reportedData) { ++ this.reportedData = reportedData; ++ } ++ ++ /** ++ * Adds a new field as part of the form. ++ * ++ * @param field the field to add to the form. ++ */ ++ public void addField(FormField field) { ++ synchronized (fields) { ++ fields.add(field); ++ } ++ } ++ ++ /** ++ * Adds a new instruction to the list of instructions that explain how to fill out the form ++ * and what the form is about. The dataform could include multiple instructions since each ++ * instruction could not contain newlines characters. ++ * ++ * @param instruction the new instruction that explain how to fill out the form. ++ */ ++ public void addInstruction(String instruction) { ++ synchronized (instructions) { ++ instructions.add(instruction); ++ } ++ } ++ ++ /** ++ * Adds a new item returned from a search. ++ * ++ * @param item the item returned from a search. ++ */ ++ public void addItem(Item item) { ++ synchronized (items) { ++ items.add(item); ++ } ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( ++ "\" type=\"" + getType() +"\">"); ++ if (getTitle() != null) { ++ buf.append("<title>").append(getTitle()).append(""); ++ } ++ for (Iterator it=getInstructions(); it.hasNext();) { ++ buf.append("").append(it.next()).append(""); ++ } ++ // Append the list of fields returned from a search ++ if (getReportedData() != null) { ++ buf.append(getReportedData().toXML()); ++ } ++ // Loop through all the items returned from a search and append them to the string buffer ++ for (Iterator i = getItems(); i.hasNext();) { ++ Item item = (Item) i.next(); ++ buf.append(item.toXML()); ++ } ++ // Loop through all the form fields and append them to the string buffer ++ for (Iterator i = getFields(); i.hasNext();) { ++ FormField field = (FormField) i.next(); ++ buf.append(field.toXML()); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ /** ++ * ++ * Represents the fields that will be returned from a search. This information is useful when ++ * you try to use the jabber:iq:search namespace to return dynamic form information. ++ * ++ * @author Gaston Dombiak ++ */ ++ public static class ReportedData { ++ private List fields = new ArrayList(); ++ ++ public ReportedData(List fields) { ++ this.fields = fields; ++ } ++ ++ /** ++ * Returns the fields returned from a search. ++ * ++ * @return the fields returned from a search. ++ */ ++ public Iterator getFields() { ++ return Collections.unmodifiableList(new ArrayList(fields)).iterator(); ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ // Loop through all the form items and append them to the string buffer ++ for (Iterator i = getFields(); i.hasNext();) { ++ FormField field = (FormField) i.next(); ++ buf.append(field.toXML()); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ } ++ ++ /** ++ * ++ * Represents items of reported data. ++ * ++ * @author Gaston Dombiak ++ */ ++ public static class Item { ++ private List fields = new ArrayList(); ++ ++ public Item(List fields) { ++ this.fields = fields; ++ } ++ ++ /** ++ * Returns the fields that define the data that goes with the item. ++ * ++ * @return the fields that define the data that goes with the item. ++ */ ++ public Iterator getFields() { ++ return Collections.unmodifiableList(new ArrayList(fields)).iterator(); ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ // Loop through all the form items and append them to the string buffer ++ for (Iterator i = getFields(); i.hasNext();) { ++ FormField field = (FormField) i.next(); ++ buf.append(field.toXML()); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DefaultPrivateData.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DefaultPrivateData.java +new file mode 100644 +index 0000000..f415f67 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DefaultPrivateData.java +@@ -0,0 +1,137 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.Iterator; ++import java.util.Map; ++ ++/** ++ * Default implementation of the PrivateData interface. Unless a PrivateDataProvider ++ * is registered with the PrivateDataManager class, instances of this class will be ++ * returned when getting private data.

      ++ * ++ * This class provides a very simple representation of an XML sub-document. Each element ++ * is a key in a Map with its CDATA being the value. For example, given the following ++ * XML sub-document: ++ * ++ *

      ++ * <foo xmlns="http://bar.com">
      ++ *     <color>blue</color>
      ++ *     <food>pizza</food>
      ++ * </foo>
      ++ * ++ * In this case, getValue("color") would return "blue", and getValue("food") would ++ * return "pizza". This parsing mechanism mechanism is very simplistic and will not work ++ * as desired in all cases (for example, if some of the elements have attributes. In those ++ * cases, a custom {@link org.jivesoftware.smackx.provider.PrivateDataProvider} should be used. ++ * ++ * @author Matt Tucker ++ */ ++public class DefaultPrivateData implements PrivateData { ++ ++ private String elementName; ++ private String namespace; ++ private Map map; ++ ++ /** ++ * Creates a new generic private data object. ++ * ++ * @param elementName the name of the element of the XML sub-document. ++ * @param namespace the namespace of the element. ++ */ ++ public DefaultPrivateData(String elementName, String namespace) { ++ this.elementName = elementName; ++ this.namespace = namespace; ++ } ++ ++ /** ++ * Returns the XML element name of the private data sub-packet root element. ++ * ++ * @return the XML element name of the packet extension. ++ */ ++ public String getElementName() { ++ return elementName; ++ } ++ ++ /** ++ * Returns the XML namespace of the private data sub-packet root element. ++ * ++ * @return the XML namespace of the packet extension. ++ */ ++ public String getNamespace() { ++ return namespace; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(elementName).append(" xmlns=\"").append(namespace).append("\">"); ++ for (Iterator i=getNames(); i.hasNext(); ) { ++ String name = (String)i.next(); ++ String value = getValue(name); ++ buf.append("<").append(name).append(">"); ++ buf.append(value); ++ buf.append(""); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ /** ++ * Returns an Iterator for the names that can be used to get ++ * values of the private data. ++ * ++ * @return an Iterator for the names. ++ */ ++ public synchronized Iterator getNames() { ++ if (map == null) { ++ return Collections.EMPTY_LIST.iterator(); ++ } ++ return Collections.unmodifiableMap(new HashMap(map)).keySet().iterator(); ++ } ++ ++ /** ++ * Returns a value given a name. ++ * ++ * @param name the name. ++ * @return the value. ++ */ ++ public synchronized String getValue(String name) { ++ if (map == null) { ++ return null; ++ } ++ return (String)map.get(name); ++ } ++ ++ /** ++ * Sets a value given the name. ++ * ++ * @param name the name. ++ * @param value the value. ++ */ ++ public synchronized void setValue(String name, String value) { ++ if (map == null) { ++ map = new HashMap(); ++ } ++ map.put(name, value); ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DelayInfo.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DelayInfo.java +new file mode 100644 +index 0000000..f404971 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DelayInfo.java +@@ -0,0 +1,105 @@ ++/** ++ * All rights reserved. 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.smackx.packet; ++ ++import java.util.Date; ++ ++import org.jivesoftware.smack.util.StringUtils; ++ ++/** ++ * A decorator for the {@link DelayInformation} class to transparently support ++ * both the new Delay Delivery specification XEP-0203 and ++ * the old one XEP-0091. ++ * ++ * Existing code can be backward compatible. ++ * ++ * @author Robin Collier ++ */ ++public class DelayInfo extends DelayInformation ++{ ++ ++ DelayInformation wrappedInfo; ++ ++ /** ++ * Creates a new instance with given delay information. ++ * @param delay the delay information ++ */ ++ public DelayInfo(DelayInformation delay) ++ { ++ super(delay.getStamp()); ++ wrappedInfo = delay; ++ } ++ ++ @Override ++ public String getFrom() ++ { ++ return wrappedInfo.getFrom(); ++ } ++ ++ @Override ++ public String getReason() ++ { ++ return wrappedInfo.getReason(); ++ } ++ ++ @Override ++ public Date getStamp() ++ { ++ return wrappedInfo.getStamp(); ++ } ++ ++ @Override ++ public void setFrom(String from) ++ { ++ wrappedInfo.setFrom(from); ++ } ++ ++ @Override ++ public void setReason(String reason) ++ { ++ wrappedInfo.setReason(reason); ++ } ++ ++ @Override ++ public String getElementName() ++ { ++ return "delay"; ++ } ++ ++ @Override ++ public String getNamespace() ++ { ++ return "urn:xmpp:delay"; ++ } ++ ++ @Override ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( ++ "\""); ++ buf.append(" stamp=\""); ++ buf.append(StringUtils.formatXEP0082Date(getStamp())); ++ buf.append("\""); ++ if (getFrom() != null && getFrom().length() > 0) { ++ buf.append(" from=\"").append(getFrom()).append("\""); ++ } ++ buf.append(">"); ++ if (getReason() != null && getReason().length() > 0) { ++ buf.append(getReason()); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DelayInformation.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DelayInformation.java +new file mode 100644 +index 0000000..b9ab485 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DelayInformation.java +@@ -0,0 +1,149 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import java.text.DateFormat; ++import java.text.SimpleDateFormat; ++import java.util.Date; ++import java.util.TimeZone; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++/** ++ * Represents timestamp information about data stored for later delivery. A DelayInformation will ++ * always includes the timestamp when the packet was originally sent and may include more ++ * information such as the JID of the entity that originally sent the packet as well as the reason ++ * for the delay.

      ++ * ++ * For more information see XEP-0091 ++ * and XEP-0203. ++ * ++ * @author Gaston Dombiak ++ */ ++public class DelayInformation implements PacketExtension { ++ ++ /** ++ * Date format according to the obsolete XEP-0091 specification. ++ * XEP-0091 recommends to use this old format for date-time instead of ++ * the one specified in XEP-0082. ++ *

      ++ * Date formats are not synchronized. Since multiple threads access the format concurrently, ++ * it must be synchronized externally. ++ */ ++ public static final DateFormat XEP_0091_UTC_FORMAT = new SimpleDateFormat( ++ "yyyyMMdd'T'HH:mm:ss"); ++ static { ++ XEP_0091_UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); ++ } ++ ++ private Date stamp; ++ private String from; ++ private String reason; ++ ++ /** ++ * Creates a new instance with the specified timestamp. ++ * @param stamp the timestamp ++ */ ++ public DelayInformation(Date stamp) { ++ super(); ++ this.stamp = stamp; ++ } ++ ++ /** ++ * Returns the JID of the entity that originally sent the packet or that delayed the ++ * delivery of the packet or null if this information is not available. ++ * ++ * @return the JID of the entity that originally sent the packet or that delayed the ++ * delivery of the packet. ++ */ ++ public String getFrom() { ++ return from; ++ } ++ ++ /** ++ * Sets the JID of the entity that originally sent the packet or that delayed the ++ * delivery of the packet or null if this information is not available. ++ * ++ * @param from the JID of the entity that originally sent the packet. ++ */ ++ public void setFrom(String from) { ++ this.from = from; ++ } ++ ++ /** ++ * Returns the timestamp when the packet was originally sent. The returned Date is ++ * be understood as UTC. ++ * ++ * @return the timestamp when the packet was originally sent. ++ */ ++ public Date getStamp() { ++ return stamp; ++ } ++ ++ /** ++ * Returns a natural-language description of the reason for the delay or null if ++ * this information is not available. ++ * ++ * @return a natural-language description of the reason for the delay or null. ++ */ ++ public String getReason() { ++ return reason; ++ } ++ ++ /** ++ * Sets a natural-language description of the reason for the delay or null if ++ * this information is not available. ++ * ++ * @param reason a natural-language description of the reason for the delay or null. ++ */ ++ public void setReason(String reason) { ++ this.reason = reason; ++ } ++ ++ public String getElementName() { ++ return "x"; ++ } ++ ++ public String getNamespace() { ++ return "jabber:x:delay"; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( ++ "\""); ++ buf.append(" stamp=\""); ++ synchronized (XEP_0091_UTC_FORMAT) { ++ buf.append(XEP_0091_UTC_FORMAT.format(stamp)); ++ } ++ buf.append("\""); ++ if (from != null && from.length() > 0) { ++ buf.append(" from=\"").append(from).append("\""); ++ } ++ buf.append(">"); ++ if (reason != null && reason.length() > 0) { ++ buf.append(reason); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DeliveryReceipt.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DeliveryReceipt.java +new file mode 100644 +index 0000000..0291174 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DeliveryReceipt.java +@@ -0,0 +1,54 @@ ++/* ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++/** ++ * Represents a message delivery receipt entry as specified by ++ * Message Delivery Receipts. ++ * ++ * @author Georg Lukas ++ */ ++public class DeliveryReceipt implements PacketExtension ++{ ++ public static final String NAMESPACE = "urn:xmpp:receipts"; ++ ++ private String id; /// original ID of the delivered message ++ ++ public DeliveryReceipt(String id) ++ { ++ this.id = id; ++ } ++ ++ public String getId() ++ { ++ return id; ++ } ++ ++ public String getElementName() ++ { ++ return "received"; ++ } ++ ++ public String getNamespace() ++ { ++ return NAMESPACE; ++ } ++ ++ public String toXML() ++ { ++ return ""; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DeliveryReceiptRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DeliveryReceiptRequest.java +new file mode 100644 +index 0000000..c58f108 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DeliveryReceiptRequest.java +@@ -0,0 +1,40 @@ ++/* ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++/** ++ * Represents a message delivery receipt request entry as specified by ++ * Message Delivery Receipts. ++ * ++ * @author Georg Lukas ++ */ ++public class DeliveryReceiptRequest implements PacketExtension ++{ ++ public String getElementName() ++ { ++ return "request"; ++ } ++ ++ public String getNamespace() ++ { ++ return DeliveryReceipt.NAMESPACE; ++ } ++ ++ public String toXML() ++ { ++ return ""; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DiscoverInfo.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DiscoverInfo.java +new file mode 100644 +index 0000000..2d8071e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DiscoverInfo.java +@@ -0,0 +1,298 @@ ++/** ++ * $RCSfile$ ++ * $Revision: 7071 $ ++ * $Date: 2007-02-12 08:59:05 +0800 (Mon, 12 Feb 2007) $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++import java.util.concurrent.CopyOnWriteArrayList; ++ ++/** ++ * A DiscoverInfo IQ packet, which is used by XMPP clients to request and receive information ++ * to/from other XMPP entities.

      ++ * ++ * The received information may contain one or more identities of the requested XMPP entity, and ++ * a list of supported features by the requested XMPP entity. ++ * ++ * @author Gaston Dombiak ++ */ ++public class DiscoverInfo extends IQ { ++ ++ private final List features = new CopyOnWriteArrayList(); ++ private final List identities = new CopyOnWriteArrayList(); ++ private String node; ++ ++ /** ++ * Adds a new feature to the discovered information. ++ * ++ * @param feature the discovered feature ++ */ ++ public void addFeature(String feature) { ++ addFeature(new Feature(feature)); ++ } ++ ++ private void addFeature(Feature feature) { ++ synchronized (features) { ++ features.add(feature); ++ } ++ } ++ ++ /** ++ * Returns the discovered features of an XMPP entity. ++ * ++ * @return an Iterator on the discovered features of an XMPP entity ++ */ ++ public Iterator getFeatures() { ++ synchronized (features) { ++ return Collections.unmodifiableList(features).iterator(); ++ } ++ } ++ ++ /** ++ * Adds a new identity of the requested entity to the discovered information. ++ * ++ * @param identity the discovered entity's identity ++ */ ++ public void addIdentity(Identity identity) { ++ synchronized (identities) { ++ identities.add(identity); ++ } ++ } ++ ++ /** ++ * Returns the discovered identities of an XMPP entity. ++ * ++ * @return an Iterator on the discoveted identities ++ */ ++ public Iterator getIdentities() { ++ synchronized (identities) { ++ return Collections.unmodifiableList(identities).iterator(); ++ } ++ } ++ ++ /** ++ * Returns the node attribute that supplements the 'jid' attribute. A node is merely ++ * something that is associated with a JID and for which the JID can provide information.

      ++ * ++ * Node attributes SHOULD be used only when trying to provide or query information which ++ * is not directly addressable. ++ * ++ * @return the node attribute that supplements the 'jid' attribute ++ */ ++ public String getNode() { ++ return node; ++ } ++ ++ /** ++ * Sets the node attribute that supplements the 'jid' attribute. A node is merely ++ * something that is associated with a JID and for which the JID can provide information.

      ++ * ++ * Node attributes SHOULD be used only when trying to provide or query information which ++ * is not directly addressable. ++ * ++ * @param node the node attribute that supplements the 'jid' attribute ++ */ ++ public void setNode(String node) { ++ this.node = node; ++ } ++ ++ /** ++ * Returns true if the specified feature is part of the discovered information. ++ * ++ * @param feature the feature to check ++ * @return true if the requestes feature has been discovered ++ */ ++ public boolean containsFeature(String feature) { ++ for (Iterator it = getFeatures(); it.hasNext();) { ++ if (feature.equals(it.next().getVar())) ++ return true; ++ } ++ return false; ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ synchronized (identities) { ++ for (Identity identity : identities) { ++ buf.append(identity.toXML()); ++ } ++ } ++ synchronized (features) { ++ for (Feature feature : features) { ++ buf.append(feature.toXML()); ++ } ++ } ++ // Add packet extensions, if any are defined. ++ buf.append(getExtensionsXML()); ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ public DiscoverInfo clone() { ++ DiscoverInfo d = new DiscoverInfo(); ++ ++ // Set node ++ d.setNode(getNode()); ++ ++ // Copy features ++ synchronized (features) { ++ for (Feature f : features) { ++ d.addFeature(f); ++ } ++ } ++ ++ // Copy identities ++ synchronized (identities) { ++ for (Identity i : identities) { ++ d.addIdentity(i); ++ } ++ } ++ ++ // Copy extensions ++ for (PacketExtension pe : getExtensions()) { ++ d.addExtension(pe); ++ } ++ ++ return d; ++ } ++ ++ /** ++ * Represents the identity of a given XMPP entity. An entity may have many identities but all ++ * the identities SHOULD have the same name.

      ++ * ++ * Refer to Jabber::Registrar ++ * in order to get the official registry of values for the category and type ++ * attributes. ++ * ++ */ ++ public static class Identity { ++ ++ private String category; ++ private String name; ++ private String type; ++ ++ /** ++ * Creates a new identity for an XMPP entity. ++ * ++ * @param category the entity's category. ++ * @param name the entity's name. ++ */ ++ public Identity(String category, String name) { ++ this.category = category; ++ this.name = name; ++ } ++ ++ /** ++ * Returns the entity's category. To get the official registry of values for the ++ * 'category' attribute refer to Jabber::Registrar ++ * ++ * @return the entity's category. ++ */ ++ public String getCategory() { ++ return category; ++ } ++ ++ /** ++ * Returns the identity's name. ++ * ++ * @return the identity's name. ++ */ ++ public String getName() { ++ return name; ++ } ++ ++ /** ++ * Returns the entity's type. To get the official registry of values for the ++ * 'type' attribute refer to Jabber::Registrar ++ * ++ * @return the entity's type. ++ */ ++ public String getType() { ++ return type; ++ } ++ ++ /** ++ * Sets the entity's type. To get the official registry of values for the ++ * 'type' attribute refer to Jabber::Registrar ++ * ++ * @param type the identity's type. ++ */ ++ public void setType(String type) { ++ this.type = type; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ return buf.toString(); ++ } ++ } ++ ++ /** ++ * Represents the features offered by the item. This information helps requestors determine ++ * what actions are possible with regard to this item (registration, search, join, etc.) ++ * as well as specific feature types of interest, if any (e.g., for the purpose of feature ++ * negotiation). ++ */ ++ public static class Feature { ++ ++ private String variable; ++ ++ /** ++ * Creates a new feature offered by an XMPP entity or item. ++ * ++ * @param variable the feature's variable. ++ */ ++ public Feature(String variable) { ++ this.variable = variable; ++ } ++ ++ /** ++ * Returns the feature's variable. ++ * ++ * @return the feature's variable. ++ */ ++ public String getVar() { ++ return variable; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ return buf.toString(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DiscoverItems.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DiscoverItems.java +new file mode 100644 +index 0000000..9120d7e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DiscoverItems.java +@@ -0,0 +1,237 @@ ++/** ++ * $RCSfile$ ++ * $Revision: 7071 $ ++ * $Date: 2007-02-12 08:59:05 +0800 (Mon, 12 Feb 2007) $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++ ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++import java.util.concurrent.CopyOnWriteArrayList; ++ ++/** ++ * A DiscoverItems IQ packet, which is used by XMPP clients to request and receive items ++ * associated with XMPP entities.

      ++ * ++ * The items could also be queried in order to discover if they contain items inside. Some items ++ * may be addressable by its JID and others may require to be addressed by a JID and a node name. ++ * ++ * @author Gaston Dombiak ++ */ ++public class DiscoverItems extends IQ { ++ ++ private final List items = new CopyOnWriteArrayList(); ++ private String node; ++ ++ /** ++ * Adds a new item to the discovered information. ++ * ++ * @param item the discovered entity's item ++ */ ++ public void addItem(Item item) { ++ synchronized (items) { ++ items.add(item); ++ } ++ } ++ ++ /** ++ * Returns the discovered items of the queried XMPP entity. ++ * ++ * @return an Iterator on the discovered entity's items ++ */ ++ public Iterator getItems() { ++ synchronized (items) { ++ return Collections.unmodifiableList(items).iterator(); ++ } ++ } ++ ++ /** ++ * Returns the node attribute that supplements the 'jid' attribute. A node is merely ++ * something that is associated with a JID and for which the JID can provide information.

      ++ * ++ * Node attributes SHOULD be used only when trying to provide or query information which ++ * is not directly addressable. ++ * ++ * @return the node attribute that supplements the 'jid' attribute ++ */ ++ public String getNode() { ++ return node; ++ } ++ ++ /** ++ * Sets the node attribute that supplements the 'jid' attribute. A node is merely ++ * something that is associated with a JID and for which the JID can provide information.

      ++ * ++ * Node attributes SHOULD be used only when trying to provide or query information which ++ * is not directly addressable. ++ * ++ * @param node the node attribute that supplements the 'jid' attribute ++ */ ++ public void setNode(String node) { ++ this.node = node; ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ synchronized (items) { ++ for (Item item : items) { ++ buf.append(item.toXML()); ++ } ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ /** ++ * An item is associated with an XMPP Entity, usually thought of a children of the parent ++ * entity and normally are addressable as a JID.

      ++ * ++ * An item associated with an entity may not be addressable as a JID. In order to handle ++ * such items, Service Discovery uses an optional 'node' attribute that supplements the ++ * 'jid' attribute. ++ */ ++ public static class Item { ++ ++ /** ++ * Request to create or update the item. ++ */ ++ public static final String UPDATE_ACTION = "update"; ++ ++ /** ++ * Request to remove the item. ++ */ ++ public static final String REMOVE_ACTION = "remove"; ++ ++ private String entityID; ++ private String name; ++ private String node; ++ private String action; ++ ++ /** ++ * Create a new Item associated with a given entity. ++ * ++ * @param entityID the id of the entity that contains the item ++ */ ++ public Item(String entityID) { ++ this.entityID = entityID; ++ } ++ ++ /** ++ * Returns the entity's ID. ++ * ++ * @return the entity's ID. ++ */ ++ public String getEntityID() { ++ return entityID; ++ } ++ ++ /** ++ * Returns the entity's name. ++ * ++ * @return the entity's name. ++ */ ++ public String getName() { ++ return name; ++ } ++ ++ /** ++ * Sets the entity's name. ++ * ++ * @param name the entity's name. ++ */ ++ public void setName(String name) { ++ this.name = name; ++ } ++ ++ /** ++ * Returns the node attribute that supplements the 'jid' attribute. A node is merely ++ * something that is associated with a JID and for which the JID can provide information.

      ++ * ++ * Node attributes SHOULD be used only when trying to provide or query information which ++ * is not directly addressable. ++ * ++ * @return the node attribute that supplements the 'jid' attribute ++ */ ++ public String getNode() { ++ return node; ++ } ++ ++ /** ++ * Sets the node attribute that supplements the 'jid' attribute. A node is merely ++ * something that is associated with a JID and for which the JID can provide information.

      ++ * ++ * Node attributes SHOULD be used only when trying to provide or query information which ++ * is not directly addressable. ++ * ++ * @param node the node attribute that supplements the 'jid' attribute ++ */ ++ public void setNode(String node) { ++ this.node = node; ++ } ++ ++ /** ++ * Returns the action that specifies the action being taken for this item. Possible action ++ * values are: "update" and "remove". Update should either create a new entry if the node ++ * and jid combination does not already exist, or simply update an existing entry. If ++ * "remove" is used as the action, the item should be removed from persistent storage. ++ * ++ * @return the action being taken for this item ++ */ ++ public String getAction() { ++ return action; ++ } ++ ++ /** ++ * Sets the action that specifies the action being taken for this item. Possible action ++ * values are: "update" and "remove". Update should either create a new entry if the node ++ * and jid combination does not already exist, or simply update an existing entry. If ++ * "remove" is used as the action, the item should be removed from persistent storage. ++ * ++ * @param action the action being taken for this item ++ */ ++ public void setAction(String action) { ++ this.action = action; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ return buf.toString(); ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Header.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Header.java +new file mode 100644 +index 0000000..3fa8386 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Header.java +@@ -0,0 +1,59 @@ ++/* ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++/** ++ * Represents a Header entry as specified by the Stanza Headers and Internet Metadata (SHIM) ++ ++ * @author Robin Collier ++ */ ++public class Header implements PacketExtension ++{ ++ private String name; ++ private String value; ++ ++ public Header(String name, String value) ++ { ++ this.name = name; ++ this.value = value; ++ } ++ ++ public String getName() ++ { ++ return name; ++ } ++ ++ public String getValue() ++ { ++ return value; ++ } ++ ++ public String getElementName() ++ { ++ return "header"; ++ } ++ ++ public String getNamespace() ++ { ++ return HeadersExtension.NAMESPACE; ++ } ++ ++ public String toXML() ++ { ++ return "

      " + value + "
      "; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/HeadersExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/HeadersExtension.java +new file mode 100644 +index 0000000..78564db +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/HeadersExtension.java +@@ -0,0 +1,69 @@ ++/** ++ * All rights reserved. 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.smackx.packet; ++ ++import java.util.Collection; ++import java.util.Collections; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++/** ++ * Extension representing a list of headers as specified in Stanza Headers and Internet Metadata (SHIM) ++ * ++ * @see Header ++ * ++ * @author Robin Collier ++ */ ++public class HeadersExtension implements PacketExtension ++{ ++ public static final String NAMESPACE = "http://jabber.org/protocol/shim"; ++ ++ private Collection
      headers = Collections.EMPTY_LIST; ++ ++ public HeadersExtension(Collection
      headerList) ++ { ++ if (headerList != null) ++ headers = headerList; ++ } ++ ++ public Collection
      getHeaders() ++ { ++ return headers; ++ } ++ ++ public String getElementName() ++ { ++ return "headers"; ++ } ++ ++ public String getNamespace() ++ { ++ return NAMESPACE; ++ } ++ ++ public String toXML() ++ { ++ StringBuilder builder = new StringBuilder("<" + getElementName() + " xmlns='" + getNamespace() + "'>"); ++ ++ for (Header header : headers) ++ { ++ builder.append(header.toXML()); ++ } ++ builder.append("'); ++ ++ return builder.toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/LastActivity.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/LastActivity.java +new file mode 100644 +index 0000000..30c616f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/LastActivity.java +@@ -0,0 +1,157 @@ ++/** ++ * $RCSfile$ ++ * $Revision: 2407 $ ++ * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.PacketCollector; ++import org.jivesoftware.smack.SmackConfiguration; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smack.util.StringUtils; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * A last activity IQ for retrieving information about the last activity associated with a Jabber ID. ++ * LastActivity (JEP-012) allows for retrieval of how long a particular user has been idle and the ++ * message the specified when doing so. Use {@link org.jivesoftware.smackx.LastActivityManager} ++ * to get the last activity of a user. ++ * ++ * @author Derek DeMoro ++ */ ++public class LastActivity extends IQ { ++ ++ public long lastActivity = -1; ++ public String message; ++ ++ public LastActivity() { ++ setType(IQ.Type.GET); ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ ++ public void setLastActivity(long lastActivity) { ++ this.lastActivity = lastActivity; ++ } ++ ++ ++ private void setMessage(String message) { ++ this.message = message; ++ } ++ ++ /** ++ * Returns number of seconds that have passed since the user last logged out. ++ * If the user is offline, 0 will be returned. ++ * ++ * @return the number of seconds that have passed since the user last logged out. ++ */ ++ public long getIdleTime() { ++ return lastActivity; ++ } ++ ++ ++ /** ++ * Returns the status message of the last unavailable presence received from the user. ++ * ++ * @return the status message of the last unavailable presence received from the user ++ */ ++ public String getStatusMessage() { ++ return message; ++ } ++ ++ ++ /** ++ * The IQ Provider for LastActivity. ++ * ++ * @author Derek DeMoro ++ */ ++ public static class Provider implements IQProvider { ++ ++ public Provider() { ++ super(); ++ } ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ if (parser.getEventType() != XmlPullParser.START_TAG) { ++ throw new IllegalStateException("Parser not in proper position, or bad XML."); ++ } ++ ++ LastActivity lastActivity = new LastActivity(); ++ try { ++ String seconds = parser.getAttributeValue("", "seconds"); ++ String message = parser.nextText(); ++ if (seconds != null) { ++ long xmlSeconds = new Double(seconds).longValue(); ++ lastActivity.setLastActivity((int)xmlSeconds); ++ } ++ ++ if (message != null) { ++ lastActivity.setMessage(message); ++ } ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ } ++ return lastActivity; ++ } ++ } ++ ++ /** ++ * Retrieve the last activity of a particular jid. ++ * @param con the current Connection. ++ * @param jid the JID of the user. ++ * @return the LastActivity packet of the jid. ++ * @throws XMPPException thrown if a server error has occured. ++ * @deprecated This method only retreives the lapsed time since the last logout of a particular jid. ++ * Replaced by {@link org.jivesoftware.smackx.LastActivityManager#getLastActivity(Connection, String) getLastActivity} ++ */ ++ public static LastActivity getLastActivity(Connection con, String jid) throws XMPPException { ++ LastActivity activity = new LastActivity(); ++ jid = StringUtils.parseBareAddress(jid); ++ activity.setTo(jid); ++ ++ PacketCollector collector = con.createPacketCollector(new PacketIDFilter(activity.getPacketID())); ++ con.sendPacket(activity); ++ ++ LastActivity response = (LastActivity) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCAdmin.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCAdmin.java +new file mode 100644 +index 0000000..5fd76a7 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCAdmin.java +@@ -0,0 +1,237 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++import org.jivesoftware.smack.packet.IQ; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++ ++/** ++ * IQ packet that serves for kicking users, granting and revoking voice, banning users, ++ * modifying the ban list, granting and revoking membership and granting and revoking ++ * moderator privileges. All these operations are scoped by the ++ * 'http://jabber.org/protocol/muc#admin' namespace. ++ * ++ * @author Gaston Dombiak ++ */ ++public class MUCAdmin extends IQ { ++ ++ private List items = new ArrayList(); ++ ++ /** ++ * Returns an Iterator for item childs that holds information about roles, affiliation, ++ * jids and nicks. ++ * ++ * @return an Iterator for item childs that holds information about roles, affiliation, ++ * jids and nicks. ++ */ ++ public Iterator getItems() { ++ synchronized (items) { ++ return Collections.unmodifiableList(new ArrayList(items)).iterator(); ++ } ++ } ++ ++ /** ++ * Adds an item child that holds information about roles, affiliation, jids and nicks. ++ * ++ * @param item the item child that holds information about roles, affiliation, jids and nicks. ++ */ ++ public void addItem(Item item) { ++ synchronized (items) { ++ items.add(item); ++ } ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ synchronized (items) { ++ for (int i = 0; i < items.size(); i++) { ++ Item item = (Item) items.get(i); ++ buf.append(item.toXML()); ++ } ++ } ++ // Add packet extensions, if any are defined. ++ buf.append(getExtensionsXML()); ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ /** ++ * Item child that holds information about roles, affiliation, jids and nicks. ++ * ++ * @author Gaston Dombiak ++ */ ++ public static class Item { ++ private String actor; ++ private String reason; ++ private String affiliation; ++ private String jid; ++ private String nick; ++ private String role; ++ ++ /** ++ * Creates a new item child. ++ * ++ * @param affiliation the actor's affiliation to the room ++ * @param role the privilege level of an occupant within a room. ++ */ ++ public Item(String affiliation, String role) { ++ this.affiliation = affiliation; ++ this.role = role; ++ } ++ ++ /** ++ * Returns the actor (JID of an occupant in the room) that was kicked or banned. ++ * ++ * @return the JID of an occupant in the room that was kicked or banned. ++ */ ++ public String getActor() { ++ return actor; ++ } ++ ++ /** ++ * Returns the reason for the item child. The reason is optional and could be used to ++ * explain the reason why a user (occupant) was kicked or banned. ++ * ++ * @return the reason for the item child. ++ */ ++ public String getReason() { ++ return reason; ++ } ++ ++ /** ++ * Returns the occupant's affiliation to the room. The affiliation is a semi-permanent ++ * association or connection with a room. The possible affiliations are "owner", "admin", ++ * "member", and "outcast" (naturally it is also possible to have no affiliation). An ++ * affiliation lasts across a user's visits to a room. ++ * ++ * @return the actor's affiliation to the room ++ */ ++ public String getAffiliation() { ++ return affiliation; ++ } ++ ++ /** ++ * Returns the by which an occupant is identified within the context ++ * of a room. If the room is non-anonymous, the JID will be included in the item. ++ * ++ * @return the room JID by which an occupant is identified within the room. ++ */ ++ public String getJid() { ++ return jid; ++ } ++ ++ /** ++ * Returns the new nickname of an occupant that is changing his/her nickname. The new ++ * nickname is sent as part of the unavailable presence. ++ * ++ * @return the new nickname of an occupant that is changing his/her nickname. ++ */ ++ public String getNick() { ++ return nick; ++ } ++ ++ /** ++ * Returns the temporary position or privilege level of an occupant within a room. The ++ * possible roles are "moderator", "participant", and "visitor" (it is also possible to ++ * have no defined role). A role lasts only for the duration of an occupant's visit to ++ * a room. ++ * ++ * @return the privilege level of an occupant within a room. ++ */ ++ public String getRole() { ++ return role; ++ } ++ ++ /** ++ * Sets the actor (JID of an occupant in the room) that was kicked or banned. ++ * ++ * @param actor the actor (JID of an occupant in the room) that was kicked or banned. ++ */ ++ public void setActor(String actor) { ++ this.actor = actor; ++ } ++ ++ /** ++ * Sets the reason for the item child. The reason is optional and could be used to ++ * explain the reason why a user (occupant) was kicked or banned. ++ * ++ * @param reason the reason why a user (occupant) was kicked or banned. ++ */ ++ public void setReason(String reason) { ++ this.reason = reason; ++ } ++ ++ /** ++ * Sets the by which an occupant is identified within the context ++ * of a room. If the room is non-anonymous, the JID will be included in the item. ++ * ++ * @param jid the JID by which an occupant is identified within a room. ++ */ ++ public void setJid(String jid) { ++ this.jid = jid; ++ } ++ ++ /** ++ * Sets the new nickname of an occupant that is changing his/her nickname. The new ++ * nickname is sent as part of the unavailable presence. ++ * ++ * @param nick the new nickname of an occupant that is changing his/her nickname. ++ */ ++ public void setNick(String nick) { ++ this.nick = nick; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ } ++ else { ++ buf.append(">"); ++ if (getReason() != null) { ++ buf.append("").append(getReason()).append(""); ++ } ++ if (getActor() != null) { ++ buf.append(""); ++ } ++ buf.append(""); ++ } ++ return buf.toString(); ++ } ++ }; ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCInitialPresence.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCInitialPresence.java +new file mode 100644 +index 0000000..d3d2796 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCInitialPresence.java +@@ -0,0 +1,223 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++import java.text.SimpleDateFormat; ++import java.util.Date; ++import java.util.TimeZone; ++ ++/** ++ * Represents extended presence information whose sole purpose is to signal the ability of ++ * the occupant to speak the MUC protocol when joining a room. If the room requires a password ++ * then the MUCInitialPresence should include one.

      ++ * ++ * The amount of discussion history provided on entering a room (perhaps because the ++ * user is on a low-bandwidth connection or is using a small-footprint client) could be managed by ++ * setting a configured History instance to the MUCInitialPresence instance. ++ * @see MUCInitialPresence#setHistory(MUCInitialPresence.History). ++ * ++ * @author Gaston Dombiak ++ */ ++public class MUCInitialPresence implements PacketExtension { ++ ++ private String password; ++ private History history; ++ ++ public String getElementName() { ++ return "x"; ++ } ++ ++ public String getNamespace() { ++ return "http://jabber.org/protocol/muc"; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( ++ "\">"); ++ if (getPassword() != null) { ++ buf.append("").append(getPassword()).append(""); ++ } ++ if (getHistory() != null) { ++ buf.append(getHistory().toXML()); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ /** ++ * Returns the history that manages the amount of discussion history provided on ++ * entering a room. ++ * ++ * @return the history that manages the amount of discussion history provided on ++ * entering a room. ++ */ ++ public History getHistory() { ++ return history; ++ } ++ ++ /** ++ * Returns the password to use when the room requires a password. ++ * ++ * @return the password to use when the room requires a password. ++ */ ++ public String getPassword() { ++ return password; ++ } ++ ++ /** ++ * Sets the History that manages the amount of discussion history provided on ++ * entering a room. ++ * ++ * @param history that manages the amount of discussion history provided on ++ * entering a room. ++ */ ++ public void setHistory(History history) { ++ this.history = history; ++ } ++ ++ /** ++ * Sets the password to use when the room requires a password. ++ * ++ * @param password the password to use when the room requires a password. ++ */ ++ public void setPassword(String password) { ++ this.password = password; ++ } ++ ++ /** ++ * The History class controls the number of characters or messages to receive ++ * when entering a room. ++ * ++ * @author Gaston Dombiak ++ */ ++ public static class History { ++ ++ private int maxChars = -1; ++ private int maxStanzas = -1; ++ private int seconds = -1; ++ private Date since; ++ ++ /** ++ * Returns the total number of characters to receive in the history. ++ * ++ * @return total number of characters to receive in the history. ++ */ ++ public int getMaxChars() { ++ return maxChars; ++ } ++ ++ /** ++ * Returns the total number of messages to receive in the history. ++ * ++ * @return the total number of messages to receive in the history. ++ */ ++ public int getMaxStanzas() { ++ return maxStanzas; ++ } ++ ++ /** ++ * Returns the number of seconds to use to filter the messages received during that time. ++ * In other words, only the messages received in the last "X" seconds will be included in ++ * the history. ++ * ++ * @return the number of seconds to use to filter the messages received during that time. ++ */ ++ public int getSeconds() { ++ return seconds; ++ } ++ ++ /** ++ * Returns the since date to use to filter the messages received during that time. ++ * In other words, only the messages received since the datetime specified will be ++ * included in the history. ++ * ++ * @return the since date to use to filter the messages received during that time. ++ */ ++ public Date getSince() { ++ return since; ++ } ++ ++ /** ++ * Sets the total number of characters to receive in the history. ++ * ++ * @param maxChars the total number of characters to receive in the history. ++ */ ++ public void setMaxChars(int maxChars) { ++ this.maxChars = maxChars; ++ } ++ ++ /** ++ * Sets the total number of messages to receive in the history. ++ * ++ * @param maxStanzas the total number of messages to receive in the history. ++ */ ++ public void setMaxStanzas(int maxStanzas) { ++ this.maxStanzas = maxStanzas; ++ } ++ ++ /** ++ * Sets the number of seconds to use to filter the messages received during that time. ++ * In other words, only the messages received in the last "X" seconds will be included in ++ * the history. ++ * ++ * @param seconds the number of seconds to use to filter the messages received during ++ * that time. ++ */ ++ public void setSeconds(int seconds) { ++ this.seconds = seconds; ++ } ++ ++ /** ++ * Sets the since date to use to filter the messages received during that time. ++ * In other words, only the messages received since the datetime specified will be ++ * included in the history. ++ * ++ * @param since the since date to use to filter the messages received during that time. ++ */ ++ public void setSince(Date since) { ++ this.since = since; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ return buf.toString(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCOwner.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCOwner.java +new file mode 100644 +index 0000000..5fc3dd0 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCOwner.java +@@ -0,0 +1,342 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++import org.jivesoftware.smack.packet.IQ; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++ ++/** ++ * IQ packet that serves for granting and revoking ownership privileges, granting ++ * and revoking administrative privileges and destroying a room. All these operations ++ * are scoped by the 'http://jabber.org/protocol/muc#owner' namespace. ++ * ++ * @author Gaston Dombiak ++ */ ++public class MUCOwner extends IQ { ++ ++ private List items = new ArrayList(); ++ private Destroy destroy; ++ ++ /** ++ * Returns an Iterator for item childs that holds information about affiliation, ++ * jids and nicks. ++ * ++ * @return an Iterator for item childs that holds information about affiliation, ++ * jids and nicks. ++ */ ++ public Iterator getItems() { ++ synchronized (items) { ++ return Collections.unmodifiableList(new ArrayList(items)).iterator(); ++ } ++ } ++ ++ /** ++ * Returns a request to the server to destroy a room. The sender of the request ++ * should be the room's owner. If the sender of the destroy request is not the room's owner ++ * then the server will answer a "Forbidden" error. ++ * ++ * @return a request to the server to destroy a room. ++ */ ++ public Destroy getDestroy() { ++ return destroy; ++ } ++ ++ /** ++ * Sets a request to the server to destroy a room. The sender of the request ++ * should be the room's owner. If the sender of the destroy request is not the room's owner ++ * then the server will answer a "Forbidden" error. ++ * ++ * @param destroy the request to the server to destroy a room. ++ */ ++ public void setDestroy(Destroy destroy) { ++ this.destroy = destroy; ++ } ++ ++ /** ++ * Adds an item child that holds information about affiliation, jids and nicks. ++ * ++ * @param item the item child that holds information about affiliation, jids and nicks. ++ */ ++ public void addItem(Item item) { ++ synchronized (items) { ++ items.add(item); ++ } ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ synchronized (items) { ++ for (int i = 0; i < items.size(); i++) { ++ Item item = (Item) items.get(i); ++ buf.append(item.toXML()); ++ } ++ } ++ if (getDestroy() != null) { ++ buf.append(getDestroy().toXML()); ++ } ++ // Add packet extensions, if any are defined. ++ buf.append(getExtensionsXML()); ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ /** ++ * Item child that holds information about affiliation, jids and nicks. ++ * ++ * @author Gaston Dombiak ++ */ ++ public static class Item { ++ ++ private String actor; ++ private String reason; ++ private String affiliation; ++ private String jid; ++ private String nick; ++ private String role; ++ ++ /** ++ * Creates a new item child. ++ * ++ * @param affiliation the actor's affiliation to the room ++ */ ++ public Item(String affiliation) { ++ this.affiliation = affiliation; ++ } ++ ++ /** ++ * Returns the actor (JID of an occupant in the room) that was kicked or banned. ++ * ++ * @return the JID of an occupant in the room that was kicked or banned. ++ */ ++ public String getActor() { ++ return actor; ++ } ++ ++ /** ++ * Returns the reason for the item child. The reason is optional and could be used to ++ * explain the reason why a user (occupant) was kicked or banned. ++ * ++ * @return the reason for the item child. ++ */ ++ public String getReason() { ++ return reason; ++ } ++ ++ /** ++ * Returns the occupant's affiliation to the room. The affiliation is a semi-permanent ++ * association or connection with a room. The possible affiliations are "owner", "admin", ++ * "member", and "outcast" (naturally it is also possible to have no affiliation). An ++ * affiliation lasts across a user's visits to a room. ++ * ++ * @return the actor's affiliation to the room ++ */ ++ public String getAffiliation() { ++ return affiliation; ++ } ++ ++ /** ++ * Returns the by which an occupant is identified within the context ++ * of a room. If the room is non-anonymous, the JID will be included in the item. ++ * ++ * @return the room JID by which an occupant is identified within the room. ++ */ ++ public String getJid() { ++ return jid; ++ } ++ ++ /** ++ * Returns the new nickname of an occupant that is changing his/her nickname. The new ++ * nickname is sent as part of the unavailable presence. ++ * ++ * @return the new nickname of an occupant that is changing his/her nickname. ++ */ ++ public String getNick() { ++ return nick; ++ } ++ ++ /** ++ * Returns the temporary position or privilege level of an occupant within a room. The ++ * possible roles are "moderator", "participant", and "visitor" (it is also possible to ++ * have no defined role). A role lasts only for the duration of an occupant's visit to ++ * a room. ++ * ++ * @return the privilege level of an occupant within a room. ++ */ ++ public String getRole() { ++ return role; ++ } ++ ++ /** ++ * Sets the actor (JID of an occupant in the room) that was kicked or banned. ++ * ++ * @param actor the actor (JID of an occupant in the room) that was kicked or banned. ++ */ ++ public void setActor(String actor) { ++ this.actor = actor; ++ } ++ ++ /** ++ * Sets the reason for the item child. The reason is optional and could be used to ++ * explain the reason why a user (occupant) was kicked or banned. ++ * ++ * @param reason the reason why a user (occupant) was kicked or banned. ++ */ ++ public void setReason(String reason) { ++ this.reason = reason; ++ } ++ ++ /** ++ * Sets the by which an occupant is identified within the context ++ * of a room. If the room is non-anonymous, the JID will be included in the item. ++ * ++ * @param jid the JID by which an occupant is identified within a room. ++ */ ++ public void setJid(String jid) { ++ this.jid = jid; ++ } ++ ++ /** ++ * Sets the new nickname of an occupant that is changing his/her nickname. The new ++ * nickname is sent as part of the unavailable presence. ++ * ++ * @param nick the new nickname of an occupant that is changing his/her nickname. ++ */ ++ public void setNick(String nick) { ++ this.nick = nick; ++ } ++ ++ /** ++ * Sets the temporary position or privilege level of an occupant within a room. The ++ * possible roles are "moderator", "participant", and "visitor" (it is also possible to ++ * have no defined role). A role lasts only for the duration of an occupant's visit to ++ * a room. ++ * ++ * @param role the new privilege level of an occupant within a room. ++ */ ++ public void setRole(String role) { ++ this.role = role; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ } ++ else { ++ buf.append(">"); ++ if (getReason() != null) { ++ buf.append("").append(getReason()).append(""); ++ } ++ if (getActor() != null) { ++ buf.append(""); ++ } ++ buf.append(""); ++ } ++ return buf.toString(); ++ } ++ }; ++ ++ /** ++ * Represents a request to the server to destroy a room. The sender of the request ++ * should be the room's owner. If the sender of the destroy request is not the room's owner ++ * then the server will answer a "Forbidden" error. ++ * ++ * @author Gaston Dombiak ++ */ ++ public static class Destroy { ++ private String reason; ++ private String jid; ++ ++ ++ /** ++ * Returns the JID of an alternate location since the current room is being destroyed. ++ * ++ * @return the JID of an alternate location. ++ */ ++ public String getJid() { ++ return jid; ++ } ++ ++ /** ++ * Returns the reason for the room destruction. ++ * ++ * @return the reason for the room destruction. ++ */ ++ public String getReason() { ++ return reason; ++ } ++ ++ /** ++ * Sets the JID of an alternate location since the current room is being destroyed. ++ * ++ * @param jid the JID of an alternate location. ++ */ ++ public void setJid(String jid) { ++ this.jid = jid; ++ } ++ ++ /** ++ * Sets the reason for the room destruction. ++ * ++ * @param reason the reason for the room destruction. ++ */ ++ public void setReason(String reason) { ++ this.reason = reason; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ } ++ else { ++ buf.append(">"); ++ if (getReason() != null) { ++ buf.append("").append(getReason()).append(""); ++ } ++ buf.append(""); ++ } ++ return buf.toString(); ++ } ++ ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCUser.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCUser.java +new file mode 100644 +index 0000000..bfcd67c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCUser.java +@@ -0,0 +1,627 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++/** ++ * Represents extended presence information about roles, affiliations, full JIDs, ++ * or status codes scoped by the 'http://jabber.org/protocol/muc#user' namespace. ++ * ++ * @author Gaston Dombiak ++ */ ++public class MUCUser implements PacketExtension { ++ ++ private Invite invite; ++ private Decline decline; ++ private Item item; ++ private String password; ++ private Status status; ++ private Destroy destroy; ++ ++ public String getElementName() { ++ return "x"; ++ } ++ ++ public String getNamespace() { ++ return "http://jabber.org/protocol/muc#user"; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( ++ "\">"); ++ if (getInvite() != null) { ++ buf.append(getInvite().toXML()); ++ } ++ if (getDecline() != null) { ++ buf.append(getDecline().toXML()); ++ } ++ if (getItem() != null) { ++ buf.append(getItem().toXML()); ++ } ++ if (getPassword() != null) { ++ buf.append("").append(getPassword()).append(""); ++ } ++ if (getStatus() != null) { ++ buf.append(getStatus().toXML()); ++ } ++ if (getDestroy() != null) { ++ buf.append(getDestroy().toXML()); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ /** ++ * Returns the invitation for another user to a room. The sender of the invitation ++ * must be an occupant of the room. The invitation will be sent to the room which in turn ++ * will forward the invitation to the invitee. ++ * ++ * @return an invitation for another user to a room. ++ */ ++ public Invite getInvite() { ++ return invite; ++ } ++ ++ /** ++ * Returns the rejection to an invitation from another user to a room. The rejection will be ++ * sent to the room which in turn will forward the refusal to the inviter. ++ * ++ * @return a rejection to an invitation from another user to a room. ++ */ ++ public Decline getDecline() { ++ return decline; ++ } ++ ++ /** ++ * Returns the item child that holds information about roles, affiliation, jids and nicks. ++ * ++ * @return an item child that holds information about roles, affiliation, jids and nicks. ++ */ ++ public Item getItem() { ++ return item; ++ } ++ ++ /** ++ * Returns the password to use to enter Password-Protected Room. A Password-Protected Room is ++ * a room that a user cannot enter without first providing the correct password. ++ * ++ * @return the password to use to enter Password-Protected Room. ++ */ ++ public String getPassword() { ++ return password; ++ } ++ ++ /** ++ * Returns the status which holds a code that assists in presenting notification messages. ++ * ++ * @return the status which holds a code that assists in presenting notification messages. ++ */ ++ public Status getStatus() { ++ return status; ++ } ++ ++ /** ++ * Returns the notification that the room has been destroyed. After a room has been destroyed, ++ * the room occupants will receive a Presence packet of type 'unavailable' with the reason for ++ * the room destruction if provided by the room owner. ++ * ++ * @return a notification that the room has been destroyed. ++ */ ++ public Destroy getDestroy() { ++ return destroy; ++ } ++ ++ /** ++ * Sets the invitation for another user to a room. The sender of the invitation ++ * must be an occupant of the room. The invitation will be sent to the room which in turn ++ * will forward the invitation to the invitee. ++ * ++ * @param invite the invitation for another user to a room. ++ */ ++ public void setInvite(Invite invite) { ++ this.invite = invite; ++ } ++ ++ /** ++ * Sets the rejection to an invitation from another user to a room. The rejection will be ++ * sent to the room which in turn will forward the refusal to the inviter. ++ * ++ * @param decline the rejection to an invitation from another user to a room. ++ */ ++ public void setDecline(Decline decline) { ++ this.decline = decline; ++ } ++ ++ /** ++ * Sets the item child that holds information about roles, affiliation, jids and nicks. ++ * ++ * @param item the item child that holds information about roles, affiliation, jids and nicks. ++ */ ++ public void setItem(Item item) { ++ this.item = item; ++ } ++ ++ /** ++ * Sets the password to use to enter Password-Protected Room. A Password-Protected Room is ++ * a room that a user cannot enter without first providing the correct password. ++ * ++ * @param string the password to use to enter Password-Protected Room. ++ */ ++ public void setPassword(String string) { ++ password = string; ++ } ++ ++ /** ++ * Sets the status which holds a code that assists in presenting notification messages. ++ * ++ * @param status the status which holds a code that assists in presenting notification ++ * messages. ++ */ ++ public void setStatus(Status status) { ++ this.status = status; ++ } ++ ++ /** ++ * Sets the notification that the room has been destroyed. After a room has been destroyed, ++ * the room occupants will receive a Presence packet of type 'unavailable' with the reason for ++ * the room destruction if provided by the room owner. ++ * ++ * @param destroy the notification that the room has been destroyed. ++ */ ++ public void setDestroy(Destroy destroy) { ++ this.destroy = destroy; ++ } ++ ++ /** ++ * Represents an invitation for another user to a room. The sender of the invitation ++ * must be an occupant of the room. The invitation will be sent to the room which in turn ++ * will forward the invitation to the invitee. ++ * ++ * @author Gaston Dombiak ++ */ ++ public static class Invite { ++ private String reason; ++ private String from; ++ private String to; ++ ++ /** ++ * Returns the bare JID of the inviter or, optionally, the room JID. (e.g. ++ * 'crone1@shakespeare.lit/desktop'). ++ * ++ * @return the room's occupant that sent the invitation. ++ */ ++ public String getFrom() { ++ return from; ++ } ++ ++ /** ++ * Returns the message explaining the invitation. ++ * ++ * @return the message explaining the invitation. ++ */ ++ public String getReason() { ++ return reason; ++ } ++ ++ /** ++ * Returns the bare JID of the invitee. (e.g. 'hecate@shakespeare.lit') ++ * ++ * @return the bare JID of the invitee. ++ */ ++ public String getTo() { ++ return to; ++ } ++ ++ /** ++ * Sets the bare JID of the inviter or, optionally, the room JID. (e.g. ++ * 'crone1@shakespeare.lit/desktop') ++ * ++ * @param from the bare JID of the inviter or, optionally, the room JID. ++ */ ++ public void setFrom(String from) { ++ this.from = from; ++ } ++ ++ /** ++ * Sets the message explaining the invitation. ++ * ++ * @param reason the message explaining the invitation. ++ */ ++ public void setReason(String reason) { ++ this.reason = reason; ++ } ++ ++ /** ++ * Sets the bare JID of the invitee. (e.g. 'hecate@shakespeare.lit') ++ * ++ * @param to the bare JID of the invitee. ++ */ ++ public void setTo(String to) { ++ this.to = to; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ if (getReason() != null) { ++ buf.append("").append(getReason()).append(""); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ } ++ ++ /** ++ * Represents a rejection to an invitation from another user to a room. The rejection will be ++ * sent to the room which in turn will forward the refusal to the inviter. ++ * ++ * @author Gaston Dombiak ++ */ ++ public static class Decline { ++ private String reason; ++ private String from; ++ private String to; ++ ++ /** ++ * Returns the bare JID of the invitee that rejected the invitation. (e.g. ++ * 'crone1@shakespeare.lit/desktop'). ++ * ++ * @return the bare JID of the invitee that rejected the invitation. ++ */ ++ public String getFrom() { ++ return from; ++ } ++ ++ /** ++ * Returns the message explaining why the invitation was rejected. ++ * ++ * @return the message explaining the reason for the rejection. ++ */ ++ public String getReason() { ++ return reason; ++ } ++ ++ /** ++ * Returns the bare JID of the inviter. (e.g. 'hecate@shakespeare.lit') ++ * ++ * @return the bare JID of the inviter. ++ */ ++ public String getTo() { ++ return to; ++ } ++ ++ /** ++ * Sets the bare JID of the invitee that rejected the invitation. (e.g. ++ * 'crone1@shakespeare.lit/desktop'). ++ * ++ * @param from the bare JID of the invitee that rejected the invitation. ++ */ ++ public void setFrom(String from) { ++ this.from = from; ++ } ++ ++ /** ++ * Sets the message explaining why the invitation was rejected. ++ * ++ * @param reason the message explaining the reason for the rejection. ++ */ ++ public void setReason(String reason) { ++ this.reason = reason; ++ } ++ ++ /** ++ * Sets the bare JID of the inviter. (e.g. 'hecate@shakespeare.lit') ++ * ++ * @param to the bare JID of the inviter. ++ */ ++ public void setTo(String to) { ++ this.to = to; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ if (getReason() != null) { ++ buf.append("").append(getReason()).append(""); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ } ++ ++ /** ++ * Item child that holds information about roles, affiliation, jids and nicks. ++ * ++ * @author Gaston Dombiak ++ */ ++ public static class Item { ++ private String actor; ++ private String reason; ++ private String affiliation; ++ private String jid; ++ private String nick; ++ private String role; ++ ++ /** ++ * Creates a new item child. ++ * ++ * @param affiliation the actor's affiliation to the room ++ * @param role the privilege level of an occupant within a room. ++ */ ++ public Item(String affiliation, String role) { ++ this.affiliation = affiliation; ++ this.role = role; ++ } ++ ++ /** ++ * Returns the actor (JID of an occupant in the room) that was kicked or banned. ++ * ++ * @return the JID of an occupant in the room that was kicked or banned. ++ */ ++ public String getActor() { ++ return actor == null ? "" : actor; ++ } ++ ++ /** ++ * Returns the reason for the item child. The reason is optional and could be used to ++ * explain the reason why a user (occupant) was kicked or banned. ++ * ++ * @return the reason for the item child. ++ */ ++ public String getReason() { ++ return reason == null ? "" : reason; ++ } ++ ++ /** ++ * Returns the occupant's affiliation to the room. The affiliation is a semi-permanent ++ * association or connection with a room. The possible affiliations are "owner", "admin", ++ * "member", and "outcast" (naturally it is also possible to have no affiliation). An ++ * affiliation lasts across a user's visits to a room. ++ * ++ * @return the actor's affiliation to the room ++ */ ++ public String getAffiliation() { ++ return affiliation; ++ } ++ ++ /** ++ * Returns the by which an occupant is identified within the context ++ * of a room. If the room is non-anonymous, the JID will be included in the item. ++ * ++ * @return the room JID by which an occupant is identified within the room. ++ */ ++ public String getJid() { ++ return jid; ++ } ++ ++ /** ++ * Returns the new nickname of an occupant that is changing his/her nickname. The new ++ * nickname is sent as part of the unavailable presence. ++ * ++ * @return the new nickname of an occupant that is changing his/her nickname. ++ */ ++ public String getNick() { ++ return nick; ++ } ++ ++ /** ++ * Returns the temporary position or privilege level of an occupant within a room. The ++ * possible roles are "moderator", "participant", and "visitor" (it is also possible to ++ * have no defined role). A role lasts only for the duration of an occupant's visit to ++ * a room. ++ * ++ * @return the privilege level of an occupant within a room. ++ */ ++ public String getRole() { ++ return role; ++ } ++ ++ /** ++ * Sets the actor (JID of an occupant in the room) that was kicked or banned. ++ * ++ * @param actor the actor (JID of an occupant in the room) that was kicked or banned. ++ */ ++ public void setActor(String actor) { ++ this.actor = actor; ++ } ++ ++ /** ++ * Sets the reason for the item child. The reason is optional and could be used to ++ * explain the reason why a user (occupant) was kicked or banned. ++ * ++ * @param reason the reason why a user (occupant) was kicked or banned. ++ */ ++ public void setReason(String reason) { ++ this.reason = reason; ++ } ++ ++ /** ++ * Sets the by which an occupant is identified within the context ++ * of a room. If the room is non-anonymous, the JID will be included in the item. ++ * ++ * @param jid the JID by which an occupant is identified within a room. ++ */ ++ public void setJid(String jid) { ++ this.jid = jid; ++ } ++ ++ /** ++ * Sets the new nickname of an occupant that is changing his/her nickname. The new ++ * nickname is sent as part of the unavailable presence. ++ * ++ * @param nick the new nickname of an occupant that is changing his/her nickname. ++ */ ++ public void setNick(String nick) { ++ this.nick = nick; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ } ++ else { ++ buf.append(">"); ++ if (getReason() != null) { ++ buf.append("").append(getReason()).append(""); ++ } ++ if (getActor() != null) { ++ buf.append(""); ++ } ++ buf.append(""); ++ } ++ return buf.toString(); ++ } ++ } ++ ++ /** ++ * Status code assists in presenting notification messages. The following link provides the ++ * list of existing error codes (@link http://www.jabber.org/jeps/jep-0045.html#errorstatus). ++ * ++ * @author Gaston Dombiak ++ */ ++ public static class Status { ++ private String code; ++ ++ /** ++ * Creates a new instance of Status with the specified code. ++ * ++ * @param code the code that uniquely identifies the reason of the error. ++ */ ++ public Status(String code) { ++ this.code = code; ++ } ++ ++ /** ++ * Returns the code that uniquely identifies the reason of the error. The code ++ * assists in presenting notification messages. ++ * ++ * @return the code that uniquely identifies the reason of the error. ++ */ ++ public String getCode() { ++ return code; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ return buf.toString(); ++ } ++ } ++ ++ /** ++ * Represents a notification that the room has been destroyed. After a room has been destroyed, ++ * the room occupants will receive a Presence packet of type 'unavailable' with the reason for ++ * the room destruction if provided by the room owner. ++ * ++ * @author Gaston Dombiak ++ */ ++ public static class Destroy { ++ private String reason; ++ private String jid; ++ ++ ++ /** ++ * Returns the JID of an alternate location since the current room is being destroyed. ++ * ++ * @return the JID of an alternate location. ++ */ ++ public String getJid() { ++ return jid; ++ } ++ ++ /** ++ * Returns the reason for the room destruction. ++ * ++ * @return the reason for the room destruction. ++ */ ++ public String getReason() { ++ return reason; ++ } ++ ++ /** ++ * Sets the JID of an alternate location since the current room is being destroyed. ++ * ++ * @param jid the JID of an alternate location. ++ */ ++ public void setJid(String jid) { ++ this.jid = jid; ++ } ++ ++ /** ++ * Sets the reason for the room destruction. ++ * ++ * @param reason the reason for the room destruction. ++ */ ++ public void setReason(String reason) { ++ this.reason = reason; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ } ++ else { ++ buf.append(">"); ++ if (getReason() != null) { ++ buf.append("").append(getReason()).append(""); ++ } ++ buf.append(""); ++ } ++ return buf.toString(); ++ } ++ ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MessageEvent.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MessageEvent.java +new file mode 100644 +index 0000000..91d959d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MessageEvent.java +@@ -0,0 +1,335 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++ ++/** ++ * Represents message events relating to the delivery, display, composition and cancellation of ++ * messages.

      ++ * ++ * There are four message events currently defined in this namespace: ++ *

        ++ *
      1. Offline
        ++ * Indicates that the message has been stored offline by the intended recipient's server. This ++ * event is triggered only if the intended recipient's server supports offline storage, has that ++ * support enabled, and the recipient is offline when the server receives the message for delivery.
      2. ++ * ++ *
      3. Delivered
        ++ * Indicates that the message has been delivered to the recipient. This signifies that the message ++ * has reached the recipient's XMPP client, but does not necessarily mean that the message has ++ * been displayed. This event is to be raised by the XMPP client.
      4. ++ * ++ *
      5. Displayed
        ++ * Once the message has been received by the recipient's XMPP client, it may be displayed to the ++ * user. This event indicates that the message has been displayed, and is to be raised by the ++ * XMPP client. Even if a message is displayed multiple times, this event should be raised only ++ * once.
      6. ++ * ++ *
      7. Composing
        ++ * In threaded chat conversations, this indicates that the recipient is composing a reply to a ++ * message. The event is to be raised by the recipient's XMPP client. A XMPP client is allowed ++ * to raise this event multiple times in response to the same request, providing the original ++ * event is cancelled first.
      8. ++ *
      ++ * ++ * @author Gaston Dombiak ++ */ ++public class MessageEvent implements PacketExtension { ++ ++ public static final String OFFLINE = "offline"; ++ public static final String COMPOSING = "composing"; ++ public static final String DISPLAYED = "displayed"; ++ public static final String DELIVERED = "delivered"; ++ public static final String CANCELLED = "cancelled"; ++ ++ private boolean offline = false; ++ private boolean delivered = false; ++ private boolean displayed = false; ++ private boolean composing = false; ++ private boolean cancelled = true; ++ ++ private String packetID = null; ++ ++ /** ++ * Returns the XML element name of the extension sub-packet root element. ++ * Always returns "x" ++ * ++ * @return the XML element name of the packet extension. ++ */ ++ public String getElementName() { ++ return "x"; ++ } ++ ++ /** ++ * Returns the XML namespace of the extension sub-packet root element. ++ * According the specification the namespace is always "jabber:x:event" ++ * ++ * @return the XML namespace of the packet extension. ++ */ ++ public String getNamespace() { ++ return "jabber:x:event"; ++ } ++ ++ /** ++ * When the message is a request returns if the sender of the message requests to be notified ++ * when the receiver is composing a reply. ++ * When the message is a notification returns if the receiver of the message is composing a ++ * reply. ++ * ++ * @return true if the sender is requesting to be notified when composing or when notifying ++ * that the receiver of the message is composing a reply ++ */ ++ public boolean isComposing() { ++ return composing; ++ } ++ ++ /** ++ * When the message is a request returns if the sender of the message requests to be notified ++ * when the message is delivered. ++ * When the message is a notification returns if the message was delivered or not. ++ * ++ * @return true if the sender is requesting to be notified when delivered or when notifying ++ * that the message was delivered ++ */ ++ public boolean isDelivered() { ++ return delivered; ++ } ++ ++ /** ++ * When the message is a request returns if the sender of the message requests to be notified ++ * when the message is displayed. ++ * When the message is a notification returns if the message was displayed or not. ++ * ++ * @return true if the sender is requesting to be notified when displayed or when notifying ++ * that the message was displayed ++ */ ++ public boolean isDisplayed() { ++ return displayed; ++ } ++ ++ /** ++ * When the message is a request returns if the sender of the message requests to be notified ++ * when the receiver of the message is offline. ++ * When the message is a notification returns if the receiver of the message was offline. ++ * ++ * @return true if the sender is requesting to be notified when offline or when notifying ++ * that the receiver of the message is offline ++ */ ++ public boolean isOffline() { ++ return offline; ++ } ++ ++ /** ++ * When the message is a notification returns if the receiver of the message cancelled ++ * composing a reply. ++ * ++ * @return true if the receiver of the message cancelled composing a reply ++ */ ++ public boolean isCancelled() { ++ return cancelled; ++ } ++ ++ /** ++ * Returns the unique ID of the message that requested to be notified of the event. ++ * The packet id is not used when the message is a request for notifications ++ * ++ * @return the message id that requested to be notified of the event. ++ */ ++ public String getPacketID() { ++ return packetID; ++ } ++ ++ /** ++ * Returns the types of events. The type of event could be: ++ * "offline", "composing","delivered","displayed", "offline" ++ * ++ * @return an iterator over all the types of events of the MessageEvent. ++ */ ++ public Iterator getEventTypes() { ++ ArrayList allEvents = new ArrayList(); ++ if (isDelivered()) { ++ allEvents.add(MessageEvent.DELIVERED); ++ } ++ if (!isMessageEventRequest() && isCancelled()) { ++ allEvents.add(MessageEvent.CANCELLED); ++ } ++ if (isComposing()) { ++ allEvents.add(MessageEvent.COMPOSING); ++ } ++ if (isDisplayed()) { ++ allEvents.add(MessageEvent.DISPLAYED); ++ } ++ if (isOffline()) { ++ allEvents.add(MessageEvent.OFFLINE); ++ } ++ return allEvents.iterator(); ++ } ++ ++ /** ++ * When the message is a request sets if the sender of the message requests to be notified ++ * when the receiver is composing a reply. ++ * When the message is a notification sets if the receiver of the message is composing a ++ * reply. ++ * ++ * @param composing sets if the sender is requesting to be notified when composing or when ++ * notifying that the receiver of the message is composing a reply ++ */ ++ public void setComposing(boolean composing) { ++ this.composing = composing; ++ setCancelled(false); ++ } ++ ++ /** ++ * When the message is a request sets if the sender of the message requests to be notified ++ * when the message is delivered. ++ * When the message is a notification sets if the message was delivered or not. ++ * ++ * @param delivered sets if the sender is requesting to be notified when delivered or when ++ * notifying that the message was delivered ++ */ ++ public void setDelivered(boolean delivered) { ++ this.delivered = delivered; ++ setCancelled(false); ++ } ++ ++ /** ++ * When the message is a request sets if the sender of the message requests to be notified ++ * when the message is displayed. ++ * When the message is a notification sets if the message was displayed or not. ++ * ++ * @param displayed sets if the sender is requesting to be notified when displayed or when ++ * notifying that the message was displayed ++ */ ++ public void setDisplayed(boolean displayed) { ++ this.displayed = displayed; ++ setCancelled(false); ++ } ++ ++ /** ++ * When the message is a request sets if the sender of the message requests to be notified ++ * when the receiver of the message is offline. ++ * When the message is a notification sets if the receiver of the message was offline. ++ * ++ * @param offline sets if the sender is requesting to be notified when offline or when ++ * notifying that the receiver of the message is offline ++ */ ++ public void setOffline(boolean offline) { ++ this.offline = offline; ++ setCancelled(false); ++ } ++ ++ /** ++ * When the message is a notification sets if the receiver of the message cancelled ++ * composing a reply. ++ * The Cancelled event is never requested explicitly. It is requested implicitly when ++ * requesting to be notified of the Composing event. ++ * ++ * @param cancelled sets if the receiver of the message cancelled composing a reply ++ */ ++ public void setCancelled(boolean cancelled) { ++ this.cancelled = cancelled; ++ } ++ ++ /** ++ * Sets the unique ID of the message that requested to be notified of the event. ++ * The packet id is not used when the message is a request for notifications ++ * ++ * @param packetID the message id that requested to be notified of the event. ++ */ ++ public void setPacketID(String packetID) { ++ this.packetID = packetID; ++ } ++ ++ /** ++ * Returns true if this MessageEvent is a request for notifications. ++ * Returns false if this MessageEvent is a notification of an event. ++ * ++ * @return true if this message is a request for notifications. ++ */ ++ public boolean isMessageEventRequest() { ++ return this.packetID == null; ++ } ++ ++ /** ++ * Returns the XML representation of a Message Event according the specification. ++ * ++ * Usually the XML representation will be inside of a Message XML representation like ++ * in the following examples:

      ++ * ++ * Request to be notified when displayed: ++ *

      ++     * <message
      ++     *    to='romeo@montague.net/orchard'
      ++     *    from='juliet@capulet.com/balcony'
      ++     *    id='message22'>
      ++     * <x xmlns='jabber:x:event'>
      ++     *   <displayed/>
      ++     * </x>
      ++     * </message>
      ++     * 
      ++ * ++ * Notification of displayed: ++ *
      ++     * <message
      ++     *    from='romeo@montague.net/orchard'
      ++     *    to='juliet@capulet.com/balcony'>
      ++     * <x xmlns='jabber:x:event'>
      ++     *   <displayed/>
      ++     *   <id>message22</id>
      ++     * </x>
      ++     * </message>
      ++     * 
      ++ * ++ */ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( ++ "\">"); ++ // Note: Cancellation events don't specify any tag. They just send the packetID ++ ++ // Add the offline tag if the sender requests to be notified of offline events or if ++ // the target is offline ++ if (isOffline()) ++ buf.append("<").append(MessageEvent.OFFLINE).append("/>"); ++ // Add the delivered tag if the sender requests to be notified when the message is ++ // delivered or if the target notifies that the message has been delivered ++ if (isDelivered()) ++ buf.append("<").append(MessageEvent.DELIVERED).append("/>"); ++ // Add the displayed tag if the sender requests to be notified when the message is ++ // displayed or if the target notifies that the message has been displayed ++ if (isDisplayed()) ++ buf.append("<").append(MessageEvent.DISPLAYED).append("/>"); ++ // Add the composing tag if the sender requests to be notified when the target is ++ // composing a reply or if the target notifies that he/she is composing a reply ++ if (isComposing()) ++ buf.append("<").append(MessageEvent.COMPOSING).append("/>"); ++ // Add the id tag only if the MessageEvent is a notification message (not a request) ++ if (getPacketID() != null) ++ buf.append("").append(getPacketID()).append(""); ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MultipleAddresses.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MultipleAddresses.java +new file mode 100644 +index 0000000..3601a99 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MultipleAddresses.java +@@ -0,0 +1,205 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++ ++/** ++ * Packet extension that contains the list of addresses that a packet should be sent or was sent. ++ * ++ * @author Gaston Dombiak ++ */ ++public class MultipleAddresses implements PacketExtension { ++ ++ public static final String BCC = "bcc"; ++ public static final String CC = "cc"; ++ public static final String NO_REPLY = "noreply"; ++ public static final String REPLY_ROOM = "replyroom"; ++ public static final String REPLY_TO = "replyto"; ++ public static final String TO = "to"; ++ ++ ++ private List addresses = new ArrayList(); ++ ++ /** ++ * Adds a new address to which the packet is going to be sent or was sent. ++ * ++ * @param type on of the static type (BCC, CC, NO_REPLY, REPLY_ROOM, etc.) ++ * @param jid the JID address of the recipient. ++ * @param node used to specify a sub-addressable unit at a particular JID, corresponding to ++ * a Service Discovery node. ++ * @param desc used to specify human-readable information for this address. ++ * @param delivered true when the packet was already delivered to this address. ++ * @param uri used to specify an external system address, such as a sip:, sips:, or im: URI. ++ */ ++ public void addAddress(String type, String jid, String node, String desc, boolean delivered, ++ String uri) { ++ // Create a new address with the specificed configuration ++ Address address = new Address(type); ++ address.setJid(jid); ++ address.setNode(node); ++ address.setDescription(desc); ++ address.setDelivered(delivered); ++ address.setUri(uri); ++ // Add the new address to the list of multiple recipients ++ addresses.add(address); ++ } ++ ++ /** ++ * Indicate that the packet being sent should not be replied. ++ */ ++ public void setNoReply() { ++ // Create a new address with the specificed configuration ++ Address address = new Address(NO_REPLY); ++ // Add the new address to the list of multiple recipients ++ addresses.add(address); ++ } ++ ++ /** ++ * Returns the list of addresses that matches the specified type. Examples of address ++ * type are: TO, CC, BCC, etc.. ++ * ++ * @param type Examples of address type are: TO, CC, BCC, etc. ++ * @return the list of addresses that matches the specified type. ++ */ ++ public List getAddressesOfType(String type) { ++ List answer = new ArrayList(addresses.size()); ++ for (Iterator it = addresses.iterator(); it.hasNext();) { ++ Address address = (Address) it.next(); ++ if (address.getType().equals(type)) { ++ answer.add(address); ++ } ++ } ++ ++ return answer; ++ } ++ ++ public String getElementName() { ++ return "addresses"; ++ } ++ ++ public String getNamespace() { ++ return "http://jabber.org/protocol/address"; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()); ++ buf.append(" xmlns=\"").append(getNamespace()).append("\">"); ++ // Loop through all the addresses and append them to the string buffer ++ for (Iterator i = addresses.iterator(); i.hasNext();) { ++ Address address = (Address) i.next(); ++ buf.append(address.toXML()); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ public static class Address { ++ ++ private String type; ++ private String jid; ++ private String node; ++ private String description; ++ private boolean delivered; ++ private String uri; ++ ++ private Address(String type) { ++ this.type = type; ++ } ++ ++ public String getType() { ++ return type; ++ } ++ ++ public String getJid() { ++ return jid; ++ } ++ ++ private void setJid(String jid) { ++ this.jid = jid; ++ } ++ ++ public String getNode() { ++ return node; ++ } ++ ++ private void setNode(String node) { ++ this.node = node; ++ } ++ ++ public String getDescription() { ++ return description; ++ } ++ ++ private void setDescription(String description) { ++ this.description = description; ++ } ++ ++ public boolean isDelivered() { ++ return delivered; ++ } ++ ++ private void setDelivered(boolean delivered) { ++ this.delivered = delivered; ++ } ++ ++ public String getUri() { ++ return uri; ++ } ++ ++ private void setUri(String uri) { ++ this.uri = uri; ++ } ++ ++ private String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("
      0) { ++ buf.append(" desc=\""); ++ buf.append(description).append("\""); ++ } ++ if (delivered) { ++ buf.append(" delivered=\"true\""); ++ } ++ if (uri != null) { ++ buf.append(" uri=\""); ++ buf.append(uri).append("\""); ++ } ++ buf.append("/>"); ++ return buf.toString(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Nick.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Nick.java +new file mode 100644 +index 0000000..9a64eaa +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Nick.java +@@ -0,0 +1,112 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * A minimalistic implementation of a {@link PacketExtension} for nicknames. ++ * ++ * @author Guus der Kinderen, guus.der.kinderen@gmail.com ++ * @see XEP-0172: User Nickname ++ */ ++public class Nick implements PacketExtension { ++ ++ public static final String NAMESPACE = "http://jabber.org/protocol/nick"; ++ ++ public static final String ELEMENT_NAME = "nick"; ++ ++ private String name = null; ++ ++ public Nick(String name) { ++ this.name = name; ++ } ++ ++ /** ++ * The value of this nickname ++ * ++ * @return the nickname ++ */ ++ public String getName() { ++ return name; ++ } ++ ++ /** ++ * Sets the value of this nickname ++ * ++ * @param name ++ * the name to set ++ */ ++ public void setName(String name) { ++ this.name = name; ++ } ++ ++ /* ++ * (non-Javadoc) ++ * ++ * @see org.jivesoftware.smack.packet.PacketExtension#getElementName() ++ */ ++ public String getElementName() { ++ return ELEMENT_NAME; ++ } ++ ++ /* ++ * (non-Javadoc) ++ * ++ * @see org.jivesoftware.smack.packet.PacketExtension#getNamespace() ++ */ ++ public String getNamespace() { ++ return NAMESPACE; ++ } ++ ++ /* ++ * (non-Javadoc) ++ * ++ * @see org.jivesoftware.smack.packet.PacketExtension#toXML() ++ */ ++ public String toXML() { ++ final StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append( ++ NAMESPACE).append("\">"); ++ buf.append(getName()); ++ buf.append("'); ++ ++ return buf.toString(); ++ } ++ ++ public static class Provider implements PacketExtensionProvider { ++ ++ public PacketExtension parseExtension(XmlPullParser parser) ++ throws Exception { ++ ++ parser.next(); ++ final String name = parser.getText(); ++ ++ // Advance to end of extension. ++ while(parser.getEventType() != XmlPullParser.END_TAG) { ++ parser.next(); ++ } ++ ++ return new Nick(name); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/OfflineMessageInfo.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/OfflineMessageInfo.java +new file mode 100644 +index 0000000..5f9954d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/OfflineMessageInfo.java +@@ -0,0 +1,128 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * OfflineMessageInfo is an extension included in the retrieved offline messages requested by ++ * the {@link org.jivesoftware.smackx.OfflineMessageManager}. This extension includes a stamp ++ * that uniquely identifies the offline message. This stamp may be used for deleting the offline ++ * message. The stamp may be of the form UTC timestamps but it is not required to have that format. ++ * ++ * @author Gaston Dombiak ++ */ ++public class OfflineMessageInfo implements PacketExtension { ++ ++ private String node = null; ++ ++ /** ++ * Returns the XML element name of the extension sub-packet root element. ++ * Always returns "offline" ++ * ++ * @return the XML element name of the packet extension. ++ */ ++ public String getElementName() { ++ return "offline"; ++ } ++ ++ /** ++ * Returns the XML namespace of the extension sub-packet root element. ++ * According the specification the namespace is always "http://jabber.org/protocol/offline" ++ * ++ * @return the XML namespace of the packet extension. ++ */ ++ public String getNamespace() { ++ return "http://jabber.org/protocol/offline"; ++ } ++ ++ /** ++ * Returns the stamp that uniquely identifies the offline message. This stamp may ++ * be used for deleting the offline message. The stamp may be of the form UTC timestamps ++ * but it is not required to have that format. ++ * ++ * @return the stamp that uniquely identifies the offline message. ++ */ ++ public String getNode() { ++ return node; ++ } ++ ++ /** ++ * Sets the stamp that uniquely identifies the offline message. This stamp may ++ * be used for deleting the offline message. The stamp may be of the form UTC timestamps ++ * but it is not required to have that format. ++ * ++ * @param node the stamp that uniquely identifies the offline message. ++ */ ++ public void setNode(String node) { ++ this.node = node; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( ++ "\">"); ++ if (getNode() != null) ++ buf.append(""); ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ public static class Provider implements PacketExtensionProvider { ++ ++ /** ++ * Creates a new Provider. ++ * ProviderManager requires that every PacketExtensionProvider has a public, ++ * no-argument constructor ++ */ ++ public Provider() { ++ } ++ ++ /** ++ * Parses a OfflineMessageInfo packet (extension sub-packet). ++ * ++ * @param parser the XML parser, positioned at the starting element of the extension. ++ * @return a PacketExtension. ++ * @throws Exception if a parsing error occurs. ++ */ ++ public PacketExtension parseExtension(XmlPullParser parser) ++ throws Exception { ++ OfflineMessageInfo info = new OfflineMessageInfo(); ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("item")) ++ info.setNode(parser.getAttributeValue("", "node")); ++ } else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("offline")) { ++ done = true; ++ } ++ } ++ } ++ ++ return info; ++ } ++ ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/OfflineMessageRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/OfflineMessageRequest.java +new file mode 100644 +index 0000000..4357f40 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/OfflineMessageRequest.java +@@ -0,0 +1,237 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++ ++/** ++ * Represents a request to get some or all the offline messages of a user. This class can also ++ * be used for deleting some or all the offline messages of a user. ++ * ++ * @author Gaston Dombiak ++ */ ++public class OfflineMessageRequest extends IQ { ++ ++ private List items = new ArrayList(); ++ private boolean purge = false; ++ private boolean fetch = false; ++ ++ /** ++ * Returns an Iterator for item childs that holds information about offline messages to ++ * view or delete. ++ * ++ * @return an Iterator for item childs that holds information about offline messages to ++ * view or delete. ++ */ ++ public Iterator getItems() { ++ synchronized (items) { ++ return Collections.unmodifiableList(new ArrayList(items)).iterator(); ++ } ++ } ++ ++ /** ++ * Adds an item child that holds information about offline messages to view or delete. ++ * ++ * @param item the item child that holds information about offline messages to view or delete. ++ */ ++ public void addItem(Item item) { ++ synchronized (items) { ++ items.add(item); ++ } ++ } ++ ++ /** ++ * Returns true if all the offline messages of the user should be deleted. ++ * ++ * @return true if all the offline messages of the user should be deleted. ++ */ ++ public boolean isPurge() { ++ return purge; ++ } ++ ++ /** ++ * Sets if all the offline messages of the user should be deleted. ++ * ++ * @param purge true if all the offline messages of the user should be deleted. ++ */ ++ public void setPurge(boolean purge) { ++ this.purge = purge; ++ } ++ ++ /** ++ * Returns true if all the offline messages of the user should be retrieved. ++ * ++ * @return true if all the offline messages of the user should be retrieved. ++ */ ++ public boolean isFetch() { ++ return fetch; ++ } ++ ++ /** ++ * Sets if all the offline messages of the user should be retrieved. ++ * ++ * @param fetch true if all the offline messages of the user should be retrieved. ++ */ ++ public void setFetch(boolean fetch) { ++ this.fetch = fetch; ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ synchronized (items) { ++ for (int i = 0; i < items.size(); i++) { ++ Item item = (Item) items.get(i); ++ buf.append(item.toXML()); ++ } ++ } ++ if (purge) { ++ buf.append(""); ++ } ++ if (fetch) { ++ buf.append(""); ++ } ++ // Add packet extensions, if any are defined. ++ buf.append(getExtensionsXML()); ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ /** ++ * Item child that holds information about offline messages to view or delete. ++ * ++ * @author Gaston Dombiak ++ */ ++ public static class Item { ++ private String action; ++ private String jid; ++ private String node; ++ ++ /** ++ * Creates a new item child. ++ * ++ * @param node the actor's affiliation to the room ++ */ ++ public Item(String node) { ++ this.node = node; ++ } ++ ++ public String getNode() { ++ return node; ++ } ++ ++ /** ++ * Returns "view" or "remove" that indicate if the server should return the specified ++ * offline message or delete it. ++ * ++ * @return "view" or "remove" that indicate if the server should return the specified ++ * offline message or delete it. ++ */ ++ public String getAction() { ++ return action; ++ } ++ ++ /** ++ * Sets if the server should return the specified offline message or delete it. Possible ++ * values are "view" or "remove". ++ * ++ * @param action if the server should return the specified offline message or delete it. ++ */ ++ public void setAction(String action) { ++ this.action = action; ++ } ++ ++ public String getJid() { ++ return jid; ++ } ++ ++ public void setJid(String jid) { ++ this.jid = jid; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ return buf.toString(); ++ } ++ } ++ ++ public static class Provider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ OfflineMessageRequest request = new OfflineMessageRequest(); ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("item")) { ++ request.addItem(parseItem(parser)); ++ } ++ else if (parser.getName().equals("purge")) { ++ request.setPurge(true); ++ } ++ else if (parser.getName().equals("fetch")) { ++ request.setFetch(true); ++ } ++ } else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("offline")) { ++ done = true; ++ } ++ } ++ } ++ ++ return request; ++ } ++ ++ private Item parseItem(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ Item item = new Item(parser.getAttributeValue("", "node")); ++ item.setAction(parser.getAttributeValue("", "action")); ++ item.setJid(parser.getAttributeValue("", "jid")); ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("item")) { ++ done = true; ++ } ++ } ++ } ++ return item; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PEPEvent.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PEPEvent.java +new file mode 100644 +index 0000000..48f1de2 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PEPEvent.java +@@ -0,0 +1,105 @@ ++/** ++ * $RCSfile: PEPEvent.java,v $ ++ * $Revision: 1.1 $ ++ * $Date: 2007/11/03 00:14:32 $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++/** ++ * Represents XMPP Personal Event Protocol packets.

      ++ * ++ * The 'http://jabber.org/protocol/pubsub#event' namespace is used to publish personal events items from one client ++ * to subscribed clients (See XEP-163). ++ * ++ * @author Jeff Williams ++ */ ++public class PEPEvent implements PacketExtension { ++ ++ PEPItem item; ++ ++ /** ++ * Creates a new empty roster exchange package. ++ * ++ */ ++ public PEPEvent() { ++ super(); ++ } ++ ++ /** ++ * Creates a new empty roster exchange package. ++ * ++ */ ++ public PEPEvent(PEPItem item) { ++ super(); ++ ++ this.item = item; ++ } ++ ++ public void addPEPItem(PEPItem item) { ++ this.item = item; ++ } ++ ++ /** ++ * Returns the XML element name of the extension sub-packet root element. ++ * Always returns "x" ++ * ++ * @return the XML element name of the packet extension. ++ */ ++ public String getElementName() { ++ return "event"; ++ } ++ ++ /** ++ * Returns the XML namespace of the extension sub-packet root element. ++ * According the specification the namespace is always "jabber:x:roster" ++ * (which is not to be confused with the 'jabber:iq:roster' namespace ++ * ++ * @return the XML namespace of the packet extension. ++ */ ++ public String getNamespace() { ++ return "http://jabber.org/protocol/pubsub"; ++ } ++ ++ /** ++ * Returns the XML representation of a Personal Event Publish according the specification. ++ * ++ * Usually the XML representation will be inside of a Message XML representation like ++ * in the following example: ++ *

      ++     * <message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack">
      ++     *     <subject>Any subject you want</subject>
      ++     *     <body>This message contains roster items.</body>
      ++     *     <x xmlns="jabber:x:roster">
      ++     *         <item jid="gato1@gato.home"/>
      ++     *         <item jid="gato2@gato.home"/>
      ++     *     </x>
      ++     * </message>
      ++     * 
      ++ * ++ */ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append("\">"); ++ buf.append(item.toXML()); ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PEPItem.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PEPItem.java +new file mode 100644 +index 0000000..c3ff6f4 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PEPItem.java +@@ -0,0 +1,92 @@ ++/** ++ * $RCSfile: PEPItem.java,v $ ++ * $Revision: 1.2 $ ++ * $Date: 2007/11/06 02:05:09 $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++/** ++ * Represents XMPP Personal Event Protocol packets.

      ++ * ++ * The 'http://jabber.org/protocol/pubsub#event' namespace is used to publish personal events items from one client ++ * to subscribed clients (See XEP-163). ++ * ++ * @author Jeff Williams ++ */ ++public abstract class PEPItem implements PacketExtension { ++ ++ String id; ++ abstract String getNode(); ++ abstract String getItemDetailsXML(); ++ ++ /** ++ * Creates a new PEPItem. ++ * ++ */ ++ public PEPItem(String id) { ++ super(); ++ this.id = id; ++ } ++ ++ /** ++ * Returns the XML element name of the extension sub-packet root element. ++ * Always returns "x" ++ * ++ * @return the XML element name of the packet extension. ++ */ ++ public String getElementName() { ++ return "item"; ++ } ++ ++ /** ++ * Returns the XML namespace of the extension sub-packet root element. ++ * ++ * @return the XML namespace of the packet extension. ++ */ ++ public String getNamespace() { ++ return "http://jabber.org/protocol/pubsub"; ++ } ++ ++ /** ++ * Returns the XML representation of a Personal Event Publish according the specification. ++ * ++ * Usually the XML representation will be inside of a Message XML representation like ++ * in the following example: ++ *

      ++     * <message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack">
      ++     *     <subject>Any subject you want</subject>
      ++     *     <body>This message contains roster items.</body>
      ++     *     <x xmlns="jabber:x:roster">
      ++     *         <item jid="gato1@gato.home"/>
      ++     *         <item jid="gato2@gato.home"/>
      ++     *     </x>
      ++     * </message>
      ++     * 
      ++ * ++ */ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()).append(" id=\"").append(id).append("\">"); ++ buf.append(getItemDetailsXML()); ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PEPPubSub.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PEPPubSub.java +new file mode 100644 +index 0000000..420ce61 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PEPPubSub.java +@@ -0,0 +1,95 @@ ++/** ++ * $RCSfile: PEPPubSub.java,v $ ++ * $Revision: 1.2 $ ++ * $Date: 2007/11/03 04:46:52 $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++ ++/** ++ * Represents XMPP PEP/XEP-163 pubsub packets.

      ++ * ++ * The 'http://jabber.org/protocol/pubsub' namespace is used to publish personal events items from one client ++ * to subscribed clients (See XEP-163). ++ * ++ * @author Jeff Williams ++ */ ++public class PEPPubSub extends IQ { ++ ++ PEPItem item; ++ ++ /** ++ * Creates a new PubSub. ++ * ++ */ ++ public PEPPubSub(PEPItem item) { ++ super(); ++ ++ this.item = item; ++ } ++ ++ /** ++ * Returns the XML element name of the extension sub-packet root element. ++ * Always returns "x" ++ * ++ * @return the XML element name of the packet extension. ++ */ ++ public String getElementName() { ++ return "pubsub"; ++ } ++ ++ /** ++ * Returns the XML namespace of the extension sub-packet root element. ++ * According the specification the namespace is always "jabber:x:roster" ++ * (which is not to be confused with the 'jabber:iq:roster' namespace ++ * ++ * @return the XML namespace of the packet extension. ++ */ ++ public String getNamespace() { ++ return "http://jabber.org/protocol/pubsub"; ++ } ++ ++ /** ++ * Returns the XML representation of a Personal Event Publish according the specification. ++ * ++ * Usually the XML representation will be inside of a Message XML representation like ++ * in the following example: ++ *

      ++     * <message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack">
      ++     *     <subject>Any subject you want</subject>
      ++     *     <body>This message contains roster items.</body>
      ++     *     <x xmlns="jabber:x:roster">
      ++     *         <item jid="gato1@gato.home"/>
      ++     *         <item jid="gato2@gato.home"/>
      ++     *     </x>
      ++     * </message>
      ++     * 
      ++ * ++ */ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append("\">"); ++ buf.append(""); ++ buf.append(item.toXML()); ++ buf.append(""); ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PrivateData.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PrivateData.java +new file mode 100644 +index 0000000..3ddb7d5 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PrivateData.java +@@ -0,0 +1,52 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++/** ++ * Interface to represent private data. Each private data chunk is an XML sub-document ++ * with a root element name and namespace. ++ * ++ * @see org.jivesoftware.smackx.PrivateDataManager ++ * @author Matt Tucker ++ */ ++public interface PrivateData { ++ ++ /** ++ * Returns the root element name. ++ * ++ * @return the element name. ++ */ ++ public String getElementName(); ++ ++ /** ++ * Returns the root element XML namespace. ++ * ++ * @return the namespace. ++ */ ++ public String getNamespace(); ++ ++ /** ++ * Returns the XML reppresentation of the PrivateData. ++ * ++ * @return the private data as XML. ++ */ ++ public String toXML(); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/RosterExchange.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/RosterExchange.java +new file mode 100644 +index 0000000..3143991 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/RosterExchange.java +@@ -0,0 +1,181 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.Roster; ++import org.jivesoftware.smack.RosterEntry; ++import org.jivesoftware.smack.RosterGroup; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.RemoteRosterEntry; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++ ++/** ++ * Represents XMPP Roster Item Exchange packets.

      ++ * ++ * The 'jabber:x:roster' namespace (which is not to be confused with the 'jabber:iq:roster' ++ * namespace) is used to send roster items from one client to another. A roster item is sent by ++ * adding to the <message/> element an <x/> child scoped by the 'jabber:x:roster' namespace. This ++ * <x/> element may contain one or more <item/> children (one for each roster item to be sent).

      ++ * ++ * Each <item/> element may possess the following attributes:

      ++ * ++ * <jid/> -- The id of the contact being sent. This attribute is required.
      ++ * <name/> -- A natural-language nickname for the contact. This attribute is optional.

      ++ * ++ * Each <item/> element may also contain one or more <group/> children specifying the ++ * natural-language name of a user-specified group, for the purpose of categorizing this contact ++ * into one or more roster groups. ++ * ++ * @author Gaston Dombiak ++ */ ++public class RosterExchange implements PacketExtension { ++ ++ private List remoteRosterEntries = new ArrayList(); ++ ++ /** ++ * Creates a new empty roster exchange package. ++ * ++ */ ++ public RosterExchange() { ++ super(); ++ } ++ ++ /** ++ * Creates a new roster exchange package with the entries specified in roster. ++ * ++ * @param roster the roster to send to other XMPP entity. ++ */ ++ public RosterExchange(Roster roster) { ++ // Add all the roster entries to the new RosterExchange ++ for (RosterEntry rosterEntry : roster.getEntries()) { ++ this.addRosterEntry(rosterEntry); ++ } ++ } ++ ++ /** ++ * Adds a roster entry to the packet. ++ * ++ * @param rosterEntry a roster entry to add. ++ */ ++ public void addRosterEntry(RosterEntry rosterEntry) { ++ // Obtain a String[] from the roster entry groups name ++ List groupNamesList = new ArrayList(); ++ String[] groupNames; ++ for (RosterGroup group : rosterEntry.getGroups()) { ++ groupNamesList.add(group.getName()); ++ } ++ groupNames = groupNamesList.toArray(new String[groupNamesList.size()]); ++ ++ // Create a new Entry based on the rosterEntry and add it to the packet ++ RemoteRosterEntry remoteRosterEntry = new RemoteRosterEntry(rosterEntry.getUser(), ++ rosterEntry.getName(), groupNames); ++ ++ addRosterEntry(remoteRosterEntry); ++ } ++ ++ /** ++ * Adds a remote roster entry to the packet. ++ * ++ * @param remoteRosterEntry a remote roster entry to add. ++ */ ++ public void addRosterEntry(RemoteRosterEntry remoteRosterEntry) { ++ synchronized (remoteRosterEntries) { ++ remoteRosterEntries.add(remoteRosterEntry); ++ } ++ } ++ ++ /** ++ * Returns the XML element name of the extension sub-packet root element. ++ * Always returns "x" ++ * ++ * @return the XML element name of the packet extension. ++ */ ++ public String getElementName() { ++ return "x"; ++ } ++ ++ /** ++ * Returns the XML namespace of the extension sub-packet root element. ++ * According the specification the namespace is always "jabber:x:roster" ++ * (which is not to be confused with the 'jabber:iq:roster' namespace ++ * ++ * @return the XML namespace of the packet extension. ++ */ ++ public String getNamespace() { ++ return "jabber:x:roster"; ++ } ++ ++ /** ++ * Returns an Iterator for the roster entries in the packet. ++ * ++ * @return an Iterator for the roster entries in the packet. ++ */ ++ public Iterator getRosterEntries() { ++ synchronized (remoteRosterEntries) { ++ List entries = Collections.unmodifiableList(new ArrayList(remoteRosterEntries)); ++ return entries.iterator(); ++ } ++ } ++ ++ /** ++ * Returns a count of the entries in the roster exchange. ++ * ++ * @return the number of entries in the roster exchange. ++ */ ++ public int getEntryCount() { ++ return remoteRosterEntries.size(); ++ } ++ ++ /** ++ * Returns the XML representation of a Roster Item Exchange according the specification. ++ * ++ * Usually the XML representation will be inside of a Message XML representation like ++ * in the following example: ++ *

      ++     * <message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack">
      ++     *     <subject>Any subject you want</subject>
      ++     *     <body>This message contains roster items.</body>
      ++     *     <x xmlns="jabber:x:roster">
      ++     *         <item jid="gato1@gato.home"/>
      ++     *         <item jid="gato2@gato.home"/>
      ++     *     </x>
      ++     * </message>
      ++     * 
      ++ * ++ */ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( ++ "\">"); ++ // Loop through all roster entries and append them to the string buffer ++ for (Iterator i = getRosterEntries(); i.hasNext();) { ++ RemoteRosterEntry remoteRosterEntry = (RemoteRosterEntry) i.next(); ++ buf.append(remoteRosterEntry.toXML()); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/SharedGroupsInfo.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/SharedGroupsInfo.java +new file mode 100644 +index 0000000..ff0be41 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/SharedGroupsInfo.java +@@ -0,0 +1,73 @@ ++package org.jivesoftware.smackx.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++ ++/** ++ * IQ packet used for discovering the user's shared groups and for getting the answer back ++ * from the server.

      ++ * ++ * Important note: This functionality is not part of the XMPP spec and it will only work ++ * with Wildfire. ++ * ++ * @author Gaston Dombiak ++ */ ++public class SharedGroupsInfo extends IQ { ++ ++ private List groups = new ArrayList(); ++ ++ /** ++ * Returns a collection with the shared group names returned from the server. ++ * ++ * @return collection with the shared group names returned from the server. ++ */ ++ public List getGroups() { ++ return groups; ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ for (Iterator it=groups.iterator(); it.hasNext();) { ++ buf.append("").append(it.next()).append(""); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ /** ++ * Internal Search service Provider. ++ */ ++ public static class Provider implements IQProvider { ++ ++ /** ++ * Provider Constructor. ++ */ ++ public Provider() { ++ super(); ++ } ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ SharedGroupsInfo groupsInfo = new SharedGroupsInfo(); ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG && parser.getName().equals("group")) { ++ groupsInfo.getGroups().add(parser.nextText()); ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("sharedgroup")) { ++ done = true; ++ } ++ } ++ } ++ return groupsInfo; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/StreamInitiation.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/StreamInitiation.java +new file mode 100644 +index 0000000..1659f19 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/StreamInitiation.java +@@ -0,0 +1,419 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import java.util.Date; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.util.StringUtils; ++ ++/** ++ * The process by which two entities initiate a stream. ++ * ++ * @author Alexander Wenckus ++ */ ++public class StreamInitiation extends IQ { ++ ++ private String id; ++ ++ private String mimeType; ++ ++ private File file; ++ ++ private Feature featureNegotiation; ++ ++ /** ++ * The "id" attribute is an opaque identifier. This attribute MUST be ++ * present on type='set', and MUST be a valid string. This SHOULD NOT be ++ * sent back on type='result', since the "id" attribute provides the ++ * only context needed. This value is generated by the Sender, and the same ++ * value MUST be used throughout a session when talking to the Receiver. ++ * ++ * @param id The "id" attribute. ++ */ ++ public void setSesssionID(final String id) { ++ this.id = id; ++ } ++ ++ /** ++ * Uniquely identifies a stream initiation to the recipient. ++ * ++ * @return The "id" attribute. ++ * @see #setSesssionID(String) ++ */ ++ public String getSessionID() { ++ return id; ++ } ++ ++ /** ++ * The "mime-type" attribute identifies the MIME-type for the data across ++ * the stream. This attribute MUST be a valid MIME-type as registered with ++ * the Internet Assigned Numbers Authority (IANA) [3] (specifically, as ++ * listed at ). During ++ * negotiation, this attribute SHOULD be present, and is otherwise not ++ * required. If not included during negotiation, its value is assumed to be ++ * "binary/octect-stream". ++ * ++ * @param mimeType The valid mime-type. ++ */ ++ public void setMimeType(final String mimeType) { ++ this.mimeType = mimeType; ++ } ++ ++ /** ++ * Identifies the type of file that is desired to be transfered. ++ * ++ * @return The mime-type. ++ * @see #setMimeType(String) ++ */ ++ public String getMimeType() { ++ return mimeType; ++ } ++ ++ /** ++ * Sets the file which contains the information pertaining to the file to be ++ * transfered. ++ * ++ * @param file The file identified by the stream initiator to be sent. ++ */ ++ public void setFile(final File file) { ++ this.file = file; ++ } ++ ++ /** ++ * Returns the file containing the information about the request. ++ * ++ * @return Returns the file containing the information about the request. ++ */ ++ public File getFile() { ++ return file; ++ } ++ ++ /** ++ * Sets the data form which contains the valid methods of stream neotiation ++ * and transfer. ++ * ++ * @param form The dataform containing the methods. ++ */ ++ public void setFeatureNegotiationForm(final DataForm form) { ++ this.featureNegotiation = new Feature(form); ++ } ++ ++ /** ++ * Returns the data form which contains the valid methods of stream ++ * neotiation and transfer. ++ * ++ * @return Returns the data form which contains the valid methods of stream ++ * neotiation and transfer. ++ */ ++ public DataForm getFeatureNegotiationForm() { ++ return featureNegotiation.getData(); ++ } ++ ++ /* ++ * (non-Javadoc) ++ * ++ * @see org.jivesoftware.smack.packet.IQ#getChildElementXML() ++ */ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ if (this.getType().equals(IQ.Type.SET)) { ++ buf.append(""); ++ ++ // Add the file section if there is one. ++ String fileXML = file.toXML(); ++ if (fileXML != null) { ++ buf.append(fileXML); ++ } ++ } ++ else if (this.getType().equals(IQ.Type.RESULT)) { ++ buf.append(""); ++ } ++ else { ++ throw new IllegalArgumentException("IQ Type not understood"); ++ } ++ if (featureNegotiation != null) { ++ buf.append(featureNegotiation.toXML()); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ /** ++ *

        ++ *
      • size: The size, in bytes, of the data to be sent.
      • ++ *
      • name: The name of the file that the Sender wishes to send.
      • ++ *
      • date: The last modification time of the file. This is specified ++ * using the DateTime profile as described in Jabber Date and Time Profiles.
      • ++ *
      • hash: The MD5 sum of the file contents.
      • ++ *
      ++ *

      ++ *

      ++ * <desc> is used to provide a sender-generated description of the ++ * file so the receiver can better understand what is being sent. It MUST ++ * NOT be sent in the result. ++ *

      ++ *

      ++ * When <range> is sent in the offer, it should have no attributes. ++ * This signifies that the sender can do ranged transfers. When a Stream ++ * Initiation result is sent with the element, it uses these ++ * attributes: ++ *

      ++ *

        ++ *
      • offset: Specifies the position, in bytes, to start transferring the ++ * file data from. This defaults to zero (0) if not specified.
      • ++ *
      • length - Specifies the number of bytes to retrieve starting at ++ * offset. This defaults to the length of the file from offset to the end.
      • ++ *
      ++ *

      ++ *

      ++ * Both attributes are OPTIONAL on the <range> element. Sending no ++ * attributes is synonymous with not sending the <range> element. When ++ * no <range> element is sent in the Stream Initiation result, the ++ * Sender MUST send the complete file starting at offset 0. More generally, ++ * data is sent over the stream byte for byte starting at the offset ++ * position for the length specified. ++ * ++ * @author Alexander Wenckus ++ */ ++ public static class File implements PacketExtension { ++ ++ private final String name; ++ ++ private final long size; ++ ++ private String hash; ++ ++ private Date date; ++ ++ private String desc; ++ ++ private boolean isRanged; ++ ++ /** ++ * Constructor providing the name of the file and its size. ++ * ++ * @param name The name of the file. ++ * @param size The size of the file in bytes. ++ */ ++ public File(final String name, final long size) { ++ if (name == null) { ++ throw new NullPointerException("name cannot be null"); ++ } ++ ++ this.name = name; ++ this.size = size; ++ } ++ ++ /** ++ * Returns the file's name. ++ * ++ * @return Returns the file's name. ++ */ ++ public String getName() { ++ return name; ++ } ++ ++ /** ++ * Returns the file's size. ++ * ++ * @return Returns the file's size. ++ */ ++ public long getSize() { ++ return size; ++ } ++ ++ /** ++ * Sets the MD5 sum of the file's contents ++ * ++ * @param hash The MD5 sum of the file's contents. ++ */ ++ public void setHash(final String hash) { ++ this.hash = hash; ++ } ++ ++ /** ++ * Returns the MD5 sum of the file's contents ++ * ++ * @return Returns the MD5 sum of the file's contents ++ */ ++ public String getHash() { ++ return hash; ++ } ++ ++ /** ++ * Sets the date that the file was last modified. ++ * ++ * @param date The date that the file was last modified. ++ */ ++ public void setDate(Date date) { ++ this.date = date; ++ } ++ ++ /** ++ * Returns the date that the file was last modified. ++ * ++ * @return Returns the date that the file was last modified. ++ */ ++ public Date getDate() { ++ return date; ++ } ++ ++ /** ++ * Sets the description of the file. ++ * ++ * @param desc The description of the file so that the file reciever can ++ * know what file it is. ++ */ ++ public void setDesc(final String desc) { ++ this.desc = desc; ++ } ++ ++ /** ++ * Returns the description of the file. ++ * ++ * @return Returns the description of the file. ++ */ ++ public String getDesc() { ++ return desc; ++ } ++ ++ /** ++ * True if a range can be provided and false if it cannot. ++ * ++ * @param isRanged True if a range can be provided and false if it cannot. ++ */ ++ public void setRanged(final boolean isRanged) { ++ this.isRanged = isRanged; ++ } ++ ++ /** ++ * Returns whether or not the initiator can support a range for the file ++ * tranfer. ++ * ++ * @return Returns whether or not the initiator can support a range for ++ * the file tranfer. ++ */ ++ public boolean isRanged() { ++ return isRanged; ++ } ++ ++ public String getElementName() { ++ return "file"; ++ } ++ ++ public String getNamespace() { ++ return "http://jabber.org/protocol/si/profile/file-transfer"; ++ } ++ ++ public String toXML() { ++ StringBuilder buffer = new StringBuilder(); ++ ++ buffer.append("<").append(getElementName()).append(" xmlns=\"") ++ .append(getNamespace()).append("\" "); ++ ++ if (getName() != null) { ++ buffer.append("name=\"").append(StringUtils.escapeForXML(getName())).append("\" "); ++ } ++ ++ if (getSize() > 0) { ++ buffer.append("size=\"").append(getSize()).append("\" "); ++ } ++ ++ if (getDate() != null) { ++ buffer.append("date=\"").append(StringUtils.formatXEP0082Date(date)).append("\" "); ++ } ++ ++ if (getHash() != null) { ++ buffer.append("hash=\"").append(getHash()).append("\" "); ++ } ++ ++ if ((desc != null && desc.length() > 0) || isRanged) { ++ buffer.append(">"); ++ if (getDesc() != null && desc.length() > 0) { ++ buffer.append("").append(StringUtils.escapeForXML(getDesc())).append(""); ++ } ++ if (isRanged()) { ++ buffer.append(""); ++ } ++ buffer.append(""); ++ } ++ else { ++ buffer.append("/>"); ++ } ++ return buffer.toString(); ++ } ++ } ++ ++ /** ++ * The feature negotiation portion of the StreamInitiation packet. ++ * ++ * @author Alexander Wenckus ++ * ++ */ ++ public class Feature implements PacketExtension { ++ ++ private final DataForm data; ++ ++ /** ++ * The dataform can be provided as part of the constructor. ++ * ++ * @param data The dataform. ++ */ ++ public Feature(final DataForm data) { ++ this.data = data; ++ } ++ ++ /** ++ * Returns the dataform associated with the feature negotiation. ++ * ++ * @return Returns the dataform associated with the feature negotiation. ++ */ ++ public DataForm getData() { ++ return data; ++ } ++ ++ public String getNamespace() { ++ return "http://jabber.org/protocol/feature-neg"; ++ } ++ ++ public String getElementName() { ++ return "feature"; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf ++ .append(""); ++ buf.append(data.toXML()); ++ buf.append(""); ++ return buf.toString(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Time.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Time.java +new file mode 100644 +index 0000000..5294e77 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Time.java +@@ -0,0 +1,198 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++ ++import java.text.DateFormat; ++import java.text.SimpleDateFormat; ++import java.util.Calendar; ++import java.util.Date; ++import java.util.TimeZone; ++ ++/** ++ * A Time IQ packet, which is used by XMPP clients to exchange their respective local ++ * times. Clients that wish to fully support the entitity time protocol should register ++ * a PacketListener for incoming time requests that then respond with the local time. ++ * This class can be used to request the time from other clients, such as in the ++ * following code snippet: ++ * ++ *

      ++ * // Request the time from a remote user.
      ++ * Time timeRequest = new Time();
      ++ * timeRequest.setType(IQ.Type.GET);
      ++ * timeRequest.setTo(someUser@example.com/resource);
      ++ *
      ++ * // Create a packet collector to listen for a response.
      ++ * PacketCollector collector = con.createPacketCollector(
      ++ *                new PacketIDFilter(timeRequest.getPacketID()));
      ++ *
      ++ * con.sendPacket(timeRequest);
      ++ *
      ++ * // Wait up to 5 seconds for a result.
      ++ * IQ result = (IQ)collector.nextResult(5000);
      ++ * if (result != null && result.getType() == IQ.Type.RESULT) {
      ++ *     Time timeResult = (Time)result;
      ++ *     // Do something with result...
      ++ * }

      ++ * ++ * Warning: this is an non-standard protocol documented by ++ * XEP-0090. Because this is a ++ * non-standard protocol, it is subject to change. ++ * ++ * @author Matt Tucker ++ */ ++public class Time extends IQ { ++ ++ private static SimpleDateFormat utcFormat = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); ++ private static DateFormat displayFormat = DateFormat.getDateTimeInstance(); ++ ++ private String utc = null; ++ private String tz = null; ++ private String display = null; ++ ++ /** ++ * Creates a new Time instance with empty values for all fields. ++ */ ++ public Time() { ++ ++ } ++ ++ /** ++ * Creates a new Time instance using the specified calendar instance as ++ * the time value to send. ++ * ++ * @param cal the time value. ++ */ ++ public Time(Calendar cal) { ++ TimeZone timeZone = cal.getTimeZone(); ++ tz = cal.getTimeZone().getID(); ++ display = displayFormat.format(cal.getTime()); ++ // Convert local time to the UTC time. ++ utc = utcFormat.format(new Date( ++ cal.getTimeInMillis() - timeZone.getOffset(cal.getTimeInMillis()))); ++ } ++ ++ /** ++ * Returns the local time or null if the time hasn't been set. ++ * ++ * @return the lcocal time. ++ */ ++ public Date getTime() { ++ if (utc == null) { ++ return null; ++ } ++ Date date = null; ++ try { ++ Calendar cal = Calendar.getInstance(); ++ // Convert the UTC time to local time. ++ cal.setTime(new Date(utcFormat.parse(utc).getTime() + ++ cal.getTimeZone().getOffset(cal.getTimeInMillis()))); ++ date = cal.getTime(); ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ } ++ return date; ++ } ++ ++ /** ++ * Sets the time using the local time. ++ * ++ * @param time the current local time. ++ */ ++ public void setTime(Date time) { ++ // Convert local time to UTC time. ++ utc = utcFormat.format(new Date( ++ time.getTime() - TimeZone.getDefault().getOffset(time.getTime()))); ++ } ++ ++ /** ++ * Returns the time as a UTC formatted String using the format CCYYMMDDThh:mm:ss. ++ * ++ * @return the time as a UTC formatted String. ++ */ ++ public String getUtc() { ++ return utc; ++ } ++ ++ /** ++ * Sets the time using UTC formatted String in the format CCYYMMDDThh:mm:ss. ++ * ++ * @param utc the time using a formatted String. ++ */ ++ public void setUtc(String utc) { ++ this.utc = utc; ++ ++ } ++ ++ /** ++ * Returns the time zone. ++ * ++ * @return the time zone. ++ */ ++ public String getTz() { ++ return tz; ++ } ++ ++ /** ++ * Sets the time zone. ++ * ++ * @param tz the time zone. ++ */ ++ public void setTz(String tz) { ++ this.tz = tz; ++ } ++ ++ /** ++ * Returns the local (non-utc) time in human-friendly format. ++ * ++ * @return the local time in human-friendly format. ++ */ ++ public String getDisplay() { ++ return display; ++ } ++ ++ /** ++ * Sets the local time in human-friendly format. ++ * ++ * @param display the local time in human-friendly format. ++ */ ++ public void setDisplay(String display) { ++ this.display = display; ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ if (utc != null) { ++ buf.append("").append(utc).append(""); ++ } ++ if (tz != null) { ++ buf.append("").append(tz).append(""); ++ } ++ if (display != null) { ++ buf.append("").append(display).append(""); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/VCard.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/VCard.java +new file mode 100644 +index 0000000..b4cd17c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/VCard.java +@@ -0,0 +1,854 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.PacketCollector; ++import org.jivesoftware.smack.SmackConfiguration; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.XMPPError; ++import org.jivesoftware.smack.util.StringUtils; ++ ++import java.io.BufferedInputStream; ++import java.io.File; ++import java.io.FileInputStream; ++import java.io.IOException; ++import java.lang.reflect.Field; ++import java.lang.reflect.Modifier; ++import java.net.URL; ++import java.security.MessageDigest; ++import java.security.NoSuchAlgorithmException; ++import java.util.HashMap; ++import java.util.Iterator; ++import java.util.Map; ++ ++/** ++ * A VCard class for use with the ++ * SMACK jabber library.

      ++ *

      ++ * You should refer to the ++ * JEP-54 documentation.

      ++ *

      ++ * Please note that this class is incomplete but it does provide the most commonly found ++ * information in vCards. Also remember that VCard transfer is not a standard, and the protocol ++ * may change or be replaced.

      ++ *

      ++ * Usage: ++ *

      ++ * 

      ++ * // To save VCard: ++ *

      ++ * VCard vCard = new VCard(); ++ * vCard.setFirstName("kir"); ++ * vCard.setLastName("max"); ++ * vCard.setEmailHome("foo@fee.bar"); ++ * vCard.setJabberId("jabber@id.org"); ++ * vCard.setOrganization("Jetbrains, s.r.o"); ++ * vCard.setNickName("KIR"); ++ *

      ++ * vCard.setField("TITLE", "Mr"); ++ * vCard.setAddressFieldHome("STREET", "Some street"); ++ * vCard.setAddressFieldWork("CTRY", "US"); ++ * vCard.setPhoneWork("FAX", "3443233"); ++ *

      ++ * vCard.save(connection); ++ *

      ++ * // To load VCard: ++ *

      ++ * VCard vCard = new VCard(); ++ * vCard.load(conn); // load own VCard ++ * vCard.load(conn, "joe@foo.bar"); // load someone's VCard ++ *

      ++ * ++ * @author Kirill Maximov (kir@maxkir.com) ++ */ ++public class VCard extends IQ { ++ ++ /** ++ * Phone types: ++ * VOICE?, FAX?, PAGER?, MSG?, CELL?, VIDEO?, BBS?, MODEM?, ISDN?, PCS?, PREF? ++ */ ++ private Map homePhones = new HashMap(); ++ private Map workPhones = new HashMap(); ++ ++ ++ /** ++ * Address types: ++ * POSTAL?, PARCEL?, (DOM | INTL)?, PREF?, POBOX?, EXTADR?, STREET?, LOCALITY?, ++ * REGION?, PCODE?, CTRY? ++ */ ++ private Map homeAddr = new HashMap(); ++ private Map workAddr = new HashMap(); ++ ++ private String firstName; ++ private String lastName; ++ private String middleName; ++ ++ private String emailHome; ++ private String emailWork; ++ ++ private String organization; ++ private String organizationUnit; ++ ++ private String avatar; ++ ++ /** ++ * Such as DESC ROLE GEO etc.. see JEP-0054 ++ */ ++ private Map otherSimpleFields = new HashMap(); ++ ++ // fields that, as they are should not be escaped before forwarding to the server ++ private Map otherUnescapableFields = new HashMap(); ++ ++ public VCard() { ++ } ++ ++ /** ++ * Set generic VCard field. ++ * ++ * @param field value of field. Possible values: NICKNAME, PHOTO, BDAY, JABBERID, MAILER, TZ, ++ * GEO, TITLE, ROLE, LOGO, NOTE, PRODID, REV, SORT-STRING, SOUND, UID, URL, DESC. ++ */ ++ public String getField(String field) { ++ return otherSimpleFields.get(field); ++ } ++ ++ /** ++ * Set generic VCard field. ++ * ++ * @param value value of field ++ * @param field field to set. See {@link #getField(String)} ++ * @see #getField(String) ++ */ ++ public void setField(String field, String value) { ++ setField(field, value, false); ++ } ++ ++ /** ++ * Set generic, unescapable VCard field. If unescabale is set to true, XML maybe a part of the ++ * value. ++ * ++ * @param value value of field ++ * @param field field to set. See {@link #getField(String)} ++ * @param isUnescapable True if the value should not be escaped, and false if it should. ++ */ ++ public void setField(String field, String value, boolean isUnescapable) { ++ if (!isUnescapable) { ++ otherSimpleFields.put(field, value); ++ } ++ else { ++ otherUnescapableFields.put(field, value); ++ } ++ } ++ ++ public String getFirstName() { ++ return firstName; ++ } ++ ++ public void setFirstName(String firstName) { ++ this.firstName = firstName; ++ // Update FN field ++ updateFN(); ++ } ++ ++ public String getLastName() { ++ return lastName; ++ } ++ ++ public void setLastName(String lastName) { ++ this.lastName = lastName; ++ // Update FN field ++ updateFN(); ++ } ++ ++ public String getMiddleName() { ++ return middleName; ++ } ++ ++ public void setMiddleName(String middleName) { ++ this.middleName = middleName; ++ // Update FN field ++ updateFN(); ++ } ++ ++ public String getNickName() { ++ return otherSimpleFields.get("NICKNAME"); ++ } ++ ++ public void setNickName(String nickName) { ++ otherSimpleFields.put("NICKNAME", nickName); ++ } ++ ++ public String getEmailHome() { ++ return emailHome; ++ } ++ ++ public void setEmailHome(String email) { ++ this.emailHome = email; ++ } ++ ++ public String getEmailWork() { ++ return emailWork; ++ } ++ ++ public void setEmailWork(String emailWork) { ++ this.emailWork = emailWork; ++ } ++ ++ public String getJabberId() { ++ return otherSimpleFields.get("JABBERID"); ++ } ++ ++ public void setJabberId(String jabberId) { ++ otherSimpleFields.put("JABBERID", jabberId); ++ } ++ ++ public String getOrganization() { ++ return organization; ++ } ++ ++ public void setOrganization(String organization) { ++ this.organization = organization; ++ } ++ ++ public String getOrganizationUnit() { ++ return organizationUnit; ++ } ++ ++ public void setOrganizationUnit(String organizationUnit) { ++ this.organizationUnit = organizationUnit; ++ } ++ ++ /** ++ * Get home address field ++ * ++ * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, ++ * LOCALITY, REGION, PCODE, CTRY ++ */ ++ public String getAddressFieldHome(String addrField) { ++ return homeAddr.get(addrField); ++ } ++ ++ /** ++ * Set home address field ++ * ++ * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, ++ * LOCALITY, REGION, PCODE, CTRY ++ */ ++ public void setAddressFieldHome(String addrField, String value) { ++ homeAddr.put(addrField, value); ++ } ++ ++ /** ++ * Get work address field ++ * ++ * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, ++ * LOCALITY, REGION, PCODE, CTRY ++ */ ++ public String getAddressFieldWork(String addrField) { ++ return workAddr.get(addrField); ++ } ++ ++ /** ++ * Set work address field ++ * ++ * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, ++ * LOCALITY, REGION, PCODE, CTRY ++ */ ++ public void setAddressFieldWork(String addrField, String value) { ++ workAddr.put(addrField, value); ++ } ++ ++ ++ /** ++ * Set home phone number ++ * ++ * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF ++ * @param phoneNum phone number ++ */ ++ public void setPhoneHome(String phoneType, String phoneNum) { ++ homePhones.put(phoneType, phoneNum); ++ } ++ ++ /** ++ * Get home phone number ++ * ++ * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF ++ */ ++ public String getPhoneHome(String phoneType) { ++ return homePhones.get(phoneType); ++ } ++ ++ /** ++ * Set work phone number ++ * ++ * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF ++ * @param phoneNum phone number ++ */ ++ public void setPhoneWork(String phoneType, String phoneNum) { ++ workPhones.put(phoneType, phoneNum); ++ } ++ ++ /** ++ * Get work phone number ++ * ++ * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF ++ */ ++ public String getPhoneWork(String phoneType) { ++ return workPhones.get(phoneType); ++ } ++ ++ /** ++ * Set the avatar for the VCard by specifying the url to the image. ++ * ++ * @param avatarURL the url to the image(png,jpeg,gif,bmp) ++ */ ++ public void setAvatar(URL avatarURL) { ++ byte[] bytes = new byte[0]; ++ try { ++ bytes = getBytes(avatarURL); ++ } ++ catch (IOException e) { ++ e.printStackTrace(); ++ } ++ ++ setAvatar(bytes); ++ } ++ ++ /** ++ * Specify the bytes for the avatar to use. ++ * ++ * @param bytes the bytes of the avatar. ++ */ ++ public void setAvatar(byte[] bytes) { ++ if (bytes == null) { ++ // Remove avatar (if any) from mappings ++ otherUnescapableFields.remove("PHOTO"); ++ return; ++ } ++ ++ // Otherwise, add to mappings. ++ String encodedImage = StringUtils.encodeBase64(bytes); ++ avatar = encodedImage; ++ ++ setField("PHOTO", "image/jpeg" + encodedImage + "", true); ++ } ++ ++ /** ++ * Specify the bytes for the avatar to use as well as the mime type. ++ * ++ * @param bytes the bytes of the avatar. ++ * @param mimeType the mime type of the avatar. ++ */ ++ public void setAvatar(byte[] bytes, String mimeType) { ++ if (bytes == null) { ++ // Remove avatar (if any) from mappings ++ otherUnescapableFields.remove("PHOTO"); ++ return; ++ } ++ ++ // Otherwise, add to mappings. ++ String encodedImage = StringUtils.encodeBase64(bytes); ++ avatar = encodedImage; ++ ++ setField("PHOTO", "" + mimeType + "" + encodedImage + "", true); ++ } ++ ++ /** ++ * Set the encoded avatar string. This is used by the provider. ++ * ++ * @param encodedAvatar the encoded avatar string. ++ */ ++ public void setEncodedImage(String encodedAvatar) { ++ //TODO Move VCard and VCardProvider into a vCard package. ++ this.avatar = encodedAvatar; ++ } ++ ++ /** ++ * Return the byte representation of the avatar(if one exists), otherwise returns null if ++ * no avatar could be found. ++ * Example 1 ++ *
      ++     * // Load Avatar from VCard
      ++     * byte[] avatarBytes = vCard.getAvatar();
      ++     * 

      ++ * // To create an ImageIcon for Swing applications ++ * ImageIcon icon = new ImageIcon(avatar); ++ *

      ++ * // To create just an image object from the bytes ++ * ByteArrayInputStream bais = new ByteArrayInputStream(avatar); ++ * try { ++ * Image image = ImageIO.read(bais); ++ * } ++ * catch (IOException e) { ++ * e.printStackTrace(); ++ * } ++ *

      ++ * ++ * @return byte representation of avatar. ++ */ ++ public byte[] getAvatar() { ++ if (avatar == null) { ++ return null; ++ } ++ return StringUtils.decodeBase64(avatar); ++ } ++ ++ /** ++ * Common code for getting the bytes of a url. ++ * ++ * @param url the url to read. ++ */ ++ public static byte[] getBytes(URL url) throws IOException { ++ final String path = url.getPath(); ++ final File file = new File(path); ++ if (file.exists()) { ++ return getFileBytes(file); ++ } ++ ++ return null; ++ } ++ ++ private static byte[] getFileBytes(File file) throws IOException { ++ BufferedInputStream bis = null; ++ try { ++ bis = new BufferedInputStream(new FileInputStream(file)); ++ int bytes = (int) file.length(); ++ byte[] buffer = new byte[bytes]; ++ int readBytes = bis.read(buffer); ++ if (readBytes != buffer.length) { ++ throw new IOException("Entire file not read"); ++ } ++ return buffer; ++ } ++ finally { ++ if (bis != null) { ++ bis.close(); ++ } ++ } ++ } ++ ++ /** ++ * Returns the SHA-1 Hash of the Avatar image. ++ * ++ * @return the SHA-1 Hash of the Avatar image. ++ */ ++ public String getAvatarHash() { ++ byte[] bytes = getAvatar(); ++ if (bytes == null) { ++ return null; ++ } ++ ++ MessageDigest digest; ++ try { ++ digest = MessageDigest.getInstance("SHA-1"); ++ } ++ catch (NoSuchAlgorithmException e) { ++ e.printStackTrace(); ++ return null; ++ } ++ ++ digest.update(bytes); ++ return StringUtils.encodeHex(digest.digest()); ++ } ++ ++ private void updateFN() { ++ StringBuilder sb = new StringBuilder(); ++ if (firstName != null) { ++ sb.append(StringUtils.escapeForXML(firstName)).append(' '); ++ } ++ if (middleName != null) { ++ sb.append(StringUtils.escapeForXML(middleName)).append(' '); ++ } ++ if (lastName != null) { ++ sb.append(StringUtils.escapeForXML(lastName)); ++ } ++ setField("FN", sb.toString()); ++ } ++ ++ /** ++ * Save this vCard for the user connected by 'connection'. Connection should be authenticated ++ * and not anonymous.

      ++ *

      ++ * NOTE: the method is asynchronous and does not wait for the returned value. ++ * ++ * @param connection the Connection to use. ++ * @throws XMPPException thrown if there was an issue setting the VCard in the server. ++ */ ++ public void save(Connection connection) throws XMPPException { ++ checkAuthenticated(connection, true); ++ ++ setType(IQ.Type.SET); ++ setFrom(connection.getUser()); ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(getPacketID())); ++ connection.sendPacket(this); ++ ++ Packet response = collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ } ++ ++ /** ++ * Load VCard information for a connected user. Connection should be authenticated ++ * and not anonymous. ++ */ ++ public void load(Connection connection) throws XMPPException { ++ checkAuthenticated(connection, true); ++ ++ setFrom(connection.getUser()); ++ doLoad(connection, connection.getUser()); ++ } ++ ++ /** ++ * Load VCard information for a given user. Connection should be authenticated and not anonymous. ++ */ ++ public void load(Connection connection, String user) throws XMPPException { ++ checkAuthenticated(connection, false); ++ ++ setTo(user); ++ doLoad(connection, user); ++ } ++ ++ private void doLoad(Connection connection, String user) throws XMPPException { ++ setType(Type.GET); ++ PacketCollector collector = connection.createPacketCollector( ++ new PacketIDFilter(getPacketID())); ++ connection.sendPacket(this); ++ ++ VCard result = null; ++ try { ++ result = (VCard) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ if (result == null) { ++ String errorMessage = "Timeout getting VCard information"; ++ throw new XMPPException(errorMessage, new XMPPError( ++ XMPPError.Condition.request_timeout, errorMessage)); ++ } ++ if (result.getError() != null) { ++ throw new XMPPException(result.getError()); ++ } ++ } ++ catch (ClassCastException e) { ++ System.out.println("No VCard for " + user); ++ } ++ ++ copyFieldsFrom(result); ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder sb = new StringBuilder(); ++ new VCardWriter(sb).write(); ++ return sb.toString(); ++ } ++ ++ private void copyFieldsFrom(VCard result) { ++ if (result == null) result = new VCard(); ++ ++ Field[] fields = VCard.class.getDeclaredFields(); ++ for (Field field : fields) { ++ if (field.getDeclaringClass() == VCard.class && ++ !Modifier.isFinal(field.getModifiers())) { ++ try { ++ field.setAccessible(true); ++ field.set(this, field.get(result)); ++ } ++ catch (IllegalAccessException e) { ++ throw new RuntimeException("This cannot happen:" + field, e); ++ } ++ } ++ } ++ } ++ ++ private void checkAuthenticated(Connection connection, boolean checkForAnonymous) { ++ if (connection == null) { ++ throw new IllegalArgumentException("No connection was provided"); ++ } ++ if (!connection.isAuthenticated()) { ++ throw new IllegalArgumentException("Connection is not authenticated"); ++ } ++ if (checkForAnonymous && connection.isAnonymous()) { ++ throw new IllegalArgumentException("Connection cannot be anonymous"); ++ } ++ } ++ ++ private boolean hasContent() { ++ //noinspection OverlyComplexBooleanExpression ++ return hasNameField() ++ || hasOrganizationFields() ++ || emailHome != null ++ || emailWork != null ++ || otherSimpleFields.size() > 0 ++ || otherUnescapableFields.size() > 0 ++ || homeAddr.size() > 0 ++ || homePhones.size() > 0 ++ || workAddr.size() > 0 ++ || workPhones.size() > 0 ++ ; ++ } ++ ++ private boolean hasNameField() { ++ return firstName != null || lastName != null || middleName != null; ++ } ++ ++ private boolean hasOrganizationFields() { ++ return organization != null || organizationUnit != null; ++ } ++ ++ // Used in tests: ++ ++ public boolean equals(Object o) { ++ if (this == o) return true; ++ if (o == null || getClass() != o.getClass()) return false; ++ ++ final VCard vCard = (VCard) o; ++ ++ if (emailHome != null ? !emailHome.equals(vCard.emailHome) : vCard.emailHome != null) { ++ return false; ++ } ++ if (emailWork != null ? !emailWork.equals(vCard.emailWork) : vCard.emailWork != null) { ++ return false; ++ } ++ if (firstName != null ? !firstName.equals(vCard.firstName) : vCard.firstName != null) { ++ return false; ++ } ++ if (!homeAddr.equals(vCard.homeAddr)) { ++ return false; ++ } ++ if (!homePhones.equals(vCard.homePhones)) { ++ return false; ++ } ++ if (lastName != null ? !lastName.equals(vCard.lastName) : vCard.lastName != null) { ++ return false; ++ } ++ if (middleName != null ? !middleName.equals(vCard.middleName) : vCard.middleName != null) { ++ return false; ++ } ++ if (organization != null ? ++ !organization.equals(vCard.organization) : vCard.organization != null) { ++ return false; ++ } ++ if (organizationUnit != null ? ++ !organizationUnit.equals(vCard.organizationUnit) : vCard.organizationUnit != null) { ++ return false; ++ } ++ if (!otherSimpleFields.equals(vCard.otherSimpleFields)) { ++ return false; ++ } ++ if (!workAddr.equals(vCard.workAddr)) { ++ return false; ++ } ++ return workPhones.equals(vCard.workPhones); ++ ++ } ++ ++ public int hashCode() { ++ int result; ++ result = homePhones.hashCode(); ++ result = 29 * result + workPhones.hashCode(); ++ result = 29 * result + homeAddr.hashCode(); ++ result = 29 * result + workAddr.hashCode(); ++ result = 29 * result + (firstName != null ? firstName.hashCode() : 0); ++ result = 29 * result + (lastName != null ? lastName.hashCode() : 0); ++ result = 29 * result + (middleName != null ? middleName.hashCode() : 0); ++ result = 29 * result + (emailHome != null ? emailHome.hashCode() : 0); ++ result = 29 * result + (emailWork != null ? emailWork.hashCode() : 0); ++ result = 29 * result + (organization != null ? organization.hashCode() : 0); ++ result = 29 * result + (organizationUnit != null ? organizationUnit.hashCode() : 0); ++ result = 29 * result + otherSimpleFields.hashCode(); ++ return result; ++ } ++ ++ public String toString() { ++ return getChildElementXML(); ++ } ++ ++ //============================================================== ++ ++ private class VCardWriter { ++ ++ private final StringBuilder sb; ++ ++ VCardWriter(StringBuilder sb) { ++ this.sb = sb; ++ } ++ ++ public void write() { ++ appendTag("vCard", "xmlns", "vcard-temp", hasContent(), new ContentBuilder() { ++ public void addTagContent() { ++ buildActualContent(); ++ } ++ }); ++ } ++ ++ private void buildActualContent() { ++ if (hasNameField()) { ++ appendN(); ++ } ++ ++ appendOrganization(); ++ appendGenericFields(); ++ ++ appendEmail(emailWork, "WORK"); ++ appendEmail(emailHome, "HOME"); ++ ++ appendPhones(workPhones, "WORK"); ++ appendPhones(homePhones, "HOME"); ++ ++ appendAddress(workAddr, "WORK"); ++ appendAddress(homeAddr, "HOME"); ++ } ++ ++ private void appendEmail(final String email, final String type) { ++ if (email != null) { ++ appendTag("EMAIL", true, new ContentBuilder() { ++ public void addTagContent() { ++ appendEmptyTag(type); ++ appendEmptyTag("INTERNET"); ++ appendEmptyTag("PREF"); ++ appendTag("USERID", StringUtils.escapeForXML(email)); ++ } ++ }); ++ } ++ } ++ ++ private void appendPhones(Map phones, final String code) { ++ Iterator it = phones.entrySet().iterator(); ++ while (it.hasNext()) { ++ final Map.Entry entry = (Map.Entry) it.next(); ++ appendTag("TEL", true, new ContentBuilder() { ++ public void addTagContent() { ++ appendEmptyTag(entry.getKey()); ++ appendEmptyTag(code); ++ appendTag("NUMBER", StringUtils.escapeForXML((String) entry.getValue())); ++ } ++ }); ++ } ++ } ++ ++ private void appendAddress(final Map addr, final String code) { ++ if (addr.size() > 0) { ++ appendTag("ADR", true, new ContentBuilder() { ++ public void addTagContent() { ++ appendEmptyTag(code); ++ ++ Iterator it = addr.entrySet().iterator(); ++ while (it.hasNext()) { ++ final Map.Entry entry = (Map.Entry) it.next(); ++ appendTag((String) entry.getKey(), StringUtils.escapeForXML((String) entry.getValue())); ++ } ++ } ++ }); ++ } ++ } ++ ++ private void appendEmptyTag(Object tag) { ++ sb.append('<').append(tag).append("/>"); ++ } ++ ++ private void appendGenericFields() { ++ Iterator it = otherSimpleFields.entrySet().iterator(); ++ while (it.hasNext()) { ++ Map.Entry entry = (Map.Entry) it.next(); ++ appendTag(entry.getKey().toString(), ++ StringUtils.escapeForXML((String) entry.getValue())); ++ } ++ ++ it = otherUnescapableFields.entrySet().iterator(); ++ while (it.hasNext()) { ++ Map.Entry entry = (Map.Entry) it.next(); ++ appendTag(entry.getKey().toString(), (String) entry.getValue()); ++ } ++ } ++ ++ private void appendOrganization() { ++ if (hasOrganizationFields()) { ++ appendTag("ORG", true, new ContentBuilder() { ++ public void addTagContent() { ++ appendTag("ORGNAME", StringUtils.escapeForXML(organization)); ++ appendTag("ORGUNIT", StringUtils.escapeForXML(organizationUnit)); ++ } ++ }); ++ } ++ } ++ ++ private void appendN() { ++ appendTag("N", true, new ContentBuilder() { ++ public void addTagContent() { ++ appendTag("FAMILY", StringUtils.escapeForXML(lastName)); ++ appendTag("GIVEN", StringUtils.escapeForXML(firstName)); ++ appendTag("MIDDLE", StringUtils.escapeForXML(middleName)); ++ } ++ }); ++ } ++ ++ private void appendTag(String tag, String attr, String attrValue, boolean hasContent, ++ ContentBuilder builder) { ++ sb.append('<').append(tag); ++ if (attr != null) { ++ sb.append(' ').append(attr).append('=').append('\'').append(attrValue).append('\''); ++ } ++ ++ if (hasContent) { ++ sb.append('>'); ++ builder.addTagContent(); ++ sb.append("\n"); ++ } ++ else { ++ sb.append("/>\n"); ++ } ++ } ++ ++ private void appendTag(String tag, boolean hasContent, ContentBuilder builder) { ++ appendTag(tag, null, null, hasContent, builder); ++ } ++ ++ private void appendTag(String tag, final String tagText) { ++ if (tagText == null) return; ++ final ContentBuilder contentBuilder = new ContentBuilder() { ++ public void addTagContent() { ++ sb.append(tagText.trim()); ++ } ++ }; ++ appendTag(tag, true, contentBuilder); ++ } ++ ++ } ++ ++ //============================================================== ++ ++ private interface ContentBuilder { ++ ++ void addTagContent(); ++ } ++ ++ //============================================================== ++} ++ +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Version.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Version.java +new file mode 100644 +index 0000000..41ee419 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Version.java +@@ -0,0 +1,132 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++ ++/** ++ * A Version IQ packet, which is used by XMPP clients to discover version information ++ * about the software running at another entity's JID.

      ++ * ++ * An example to discover the version of the server: ++ *

      ++ * // Request the version from the server.
      ++ * Version versionRequest = new Version();
      ++ * timeRequest.setType(IQ.Type.GET);
      ++ * timeRequest.setTo("example.com");
      ++ *
      ++ * // Create a packet collector to listen for a response.
      ++ * PacketCollector collector = con.createPacketCollector(
      ++ *                new PacketIDFilter(versionRequest.getPacketID()));
      ++ *
      ++ * con.sendPacket(versionRequest);
      ++ *
      ++ * // Wait up to 5 seconds for a result.
      ++ * IQ result = (IQ)collector.nextResult(5000);
      ++ * if (result != null && result.getType() == IQ.Type.RESULT) {
      ++ *     Version versionResult = (Version)result;
      ++ *     // Do something with result...
      ++ * }

      ++ * ++ * @author Gaston Dombiak ++ */ ++public class Version extends IQ { ++ ++ private String name; ++ private String version; ++ private String os; ++ ++ /** ++ * Returns the natural-language name of the software. This property will always be ++ * present in a result. ++ * ++ * @return the natural-language name of the software. ++ */ ++ public String getName() { ++ return name; ++ } ++ ++ /** ++ * Sets the natural-language name of the software. This message should only be ++ * invoked when parsing the XML and setting the property to a Version instance. ++ * ++ * @param name the natural-language name of the software. ++ */ ++ public void setName(String name) { ++ this.name = name; ++ } ++ ++ /** ++ * Returns the specific version of the software. This property will always be ++ * present in a result. ++ * ++ * @return the specific version of the software. ++ */ ++ public String getVersion() { ++ return version; ++ } ++ ++ /** ++ * Sets the specific version of the software. This message should only be ++ * invoked when parsing the XML and setting the property to a Version instance. ++ * ++ * @param version the specific version of the software. ++ */ ++ public void setVersion(String version) { ++ this.version = version; ++ } ++ ++ /** ++ * Returns the operating system of the queried entity. This property will always be ++ * present in a result. ++ * ++ * @return the operating system of the queried entity. ++ */ ++ public String getOs() { ++ return os; ++ } ++ ++ /** ++ * Sets the operating system of the queried entity. This message should only be ++ * invoked when parsing the XML and setting the property to a Version instance. ++ * ++ * @param os operating system of the queried entity. ++ */ ++ public void setOs(String os) { ++ this.os = os; ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ if (name != null) { ++ buf.append("").append(name).append(""); ++ } ++ if (version != null) { ++ buf.append("").append(version).append(""); ++ } ++ if (os != null) { ++ buf.append("").append(os).append(""); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/XHTMLExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/XHTMLExtension.java +new file mode 100644 +index 0000000..449f6fe +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/XHTMLExtension.java +@@ -0,0 +1,126 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++ ++/** ++ * An XHTML sub-packet, which is used by XMPP clients to exchange formatted text. The XHTML ++ * extension is only a subset of XHTML 1.0.

      ++ * ++ * The following link summarizes the requirements of XHTML IM: ++ * Valid tags.

      ++ * ++ * Warning: this is an non-standard protocol documented by ++ * JEP-71. Because this is a ++ * non-standard protocol, it is subject to change. ++ * ++ * @author Gaston Dombiak ++ */ ++public class XHTMLExtension implements PacketExtension { ++ ++ private List bodies = new ArrayList(); ++ ++ /** ++ * Returns the XML element name of the extension sub-packet root element. ++ * Always returns "html" ++ * ++ * @return the XML element name of the packet extension. ++ */ ++ public String getElementName() { ++ return "html"; ++ } ++ ++ /** ++ * Returns the XML namespace of the extension sub-packet root element. ++ * According the specification the namespace is always "http://jabber.org/protocol/xhtml-im" ++ * ++ * @return the XML namespace of the packet extension. ++ */ ++ public String getNamespace() { ++ return "http://jabber.org/protocol/xhtml-im"; ++ } ++ ++ /** ++ * Returns the XML representation of a XHTML extension according the specification. ++ * ++ * Usually the XML representation will be inside of a Message XML representation like ++ * in the following example: ++ *

      ++     * <message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack">
      ++     *     <subject>Any subject you want</subject>
      ++     *     <body>This message contains something interesting.</body>
      ++     *     <html xmlns="http://jabber.org/protocol/xhtml-im">
      ++     *         <body><p style='font-size:large'>This message contains something <em>interesting</em>.</p></body>
      ++     *     </html>
      ++     * </message>
      ++     * 
      ++ * ++ */ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( ++ "\">"); ++ // Loop through all the bodies and append them to the string buffer ++ for (Iterator i = getBodies(); i.hasNext();) { ++ buf.append((String) i.next()); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ /** ++ * Returns an Iterator for the bodies in the packet. ++ * ++ * @return an Iterator for the bodies in the packet. ++ */ ++ public Iterator getBodies() { ++ synchronized (bodies) { ++ return Collections.unmodifiableList(new ArrayList(bodies)).iterator(); ++ } ++ } ++ ++ /** ++ * Adds a body to the packet. ++ * ++ * @param body the body to add. ++ */ ++ public void addBody(String body) { ++ synchronized (bodies) { ++ bodies.add(body); ++ } ++ } ++ ++ /** ++ * Returns a count of the bodies in the XHTML packet. ++ * ++ * @return the number of bodies in the XHTML packet. ++ */ ++ public int getBodiesCount() { ++ return bodies.size(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/package.html +new file mode 100644 +index 0000000..490d1d7 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/package.html +@@ -0,0 +1 @@ ++XML packets that are part of the XMPP extension protocols. +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/AdHocCommandDataProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/AdHocCommandDataProvider.java +new file mode 100755 +index 0000000..8d046ed +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/AdHocCommandDataProvider.java +@@ -0,0 +1,155 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2005-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.provider; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.packet.XMPPError; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.jivesoftware.smack.util.PacketParserUtils; ++import org.jivesoftware.smackx.commands.AdHocCommand; ++import org.jivesoftware.smackx.commands.AdHocCommand.Action; ++import org.jivesoftware.smackx.commands.AdHocCommandNote; ++import org.jivesoftware.smackx.packet.AdHocCommandData; ++import org.jivesoftware.smackx.packet.DataForm; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * The AdHocCommandDataProvider parses AdHocCommandData packets. ++ * ++ * @author Gabriel Guardincerri ++ */ ++public class AdHocCommandDataProvider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ AdHocCommandData adHocCommandData = new AdHocCommandData(); ++ DataFormProvider dataFormProvider = new DataFormProvider(); ++ ++ int eventType; ++ String elementName; ++ String namespace; ++ adHocCommandData.setSessionID(parser.getAttributeValue("", "sessionid")); ++ adHocCommandData.setNode(parser.getAttributeValue("", "node")); ++ ++ // Status ++ String status = parser.getAttributeValue("", "status"); ++ if (AdHocCommand.Status.executing.toString().equalsIgnoreCase(status)) { ++ adHocCommandData.setStatus(AdHocCommand.Status.executing); ++ } ++ else if (AdHocCommand.Status.completed.toString().equalsIgnoreCase(status)) { ++ adHocCommandData.setStatus(AdHocCommand.Status.completed); ++ } ++ else if (AdHocCommand.Status.canceled.toString().equalsIgnoreCase(status)) { ++ adHocCommandData.setStatus(AdHocCommand.Status.canceled); ++ } ++ ++ // Action ++ String action = parser.getAttributeValue("", "action"); ++ if (action != null) { ++ Action realAction = AdHocCommand.Action.valueOf(action); ++ if (realAction == null || realAction.equals(Action.unknown)) { ++ adHocCommandData.setAction(Action.unknown); ++ } ++ else { ++ adHocCommandData.setAction(realAction); ++ } ++ } ++ while (!done) { ++ eventType = parser.next(); ++ elementName = parser.getName(); ++ namespace = parser.getNamespace(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("actions")) { ++ String execute = parser.getAttributeValue("", "execute"); ++ if (execute != null) { ++ adHocCommandData.setExecuteAction(AdHocCommand.Action.valueOf(execute)); ++ } ++ } ++ else if (parser.getName().equals("next")) { ++ adHocCommandData.addAction(AdHocCommand.Action.next); ++ } ++ else if (parser.getName().equals("complete")) { ++ adHocCommandData.addAction(AdHocCommand.Action.complete); ++ } ++ else if (parser.getName().equals("prev")) { ++ adHocCommandData.addAction(AdHocCommand.Action.prev); ++ } ++ else if (elementName.equals("x") && namespace.equals("jabber:x:data")) { ++ adHocCommandData.setForm((DataForm) dataFormProvider.parseExtension(parser)); ++ } ++ else if (parser.getName().equals("note")) { ++ AdHocCommandNote.Type type = AdHocCommandNote.Type.valueOf( ++ parser.getAttributeValue("", "type")); ++ String value = parser.nextText(); ++ adHocCommandData.addNote(new AdHocCommandNote(type, value)); ++ } ++ else if (parser.getName().equals("error")) { ++ XMPPError error = PacketParserUtils.parseError(parser); ++ adHocCommandData.setError(error); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("command")) { ++ done = true; ++ } ++ } ++ } ++ return adHocCommandData; ++ } ++ ++ public static class BadActionError implements PacketExtensionProvider { ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ return new AdHocCommandData.SpecificError(AdHocCommand.SpecificErrorCondition.badAction); ++ } ++ } ++ ++ public static class MalformedActionError implements PacketExtensionProvider { ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ return new AdHocCommandData.SpecificError(AdHocCommand.SpecificErrorCondition.malformedAction); ++ } ++ } ++ ++ public static class BadLocaleError implements PacketExtensionProvider { ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ return new AdHocCommandData.SpecificError(AdHocCommand.SpecificErrorCondition.badLocale); ++ } ++ } ++ ++ public static class BadPayloadError implements PacketExtensionProvider { ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ return new AdHocCommandData.SpecificError(AdHocCommand.SpecificErrorCondition.badPayload); ++ } ++ } ++ ++ public static class BadSessionIDError implements PacketExtensionProvider { ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ return new AdHocCommandData.SpecificError(AdHocCommand.SpecificErrorCondition.badSessionid); ++ } ++ } ++ ++ public static class SessionExpiredError implements PacketExtensionProvider { ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ return new AdHocCommandData.SpecificError(AdHocCommand.SpecificErrorCondition.sessionExpired); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/CapsExtensionProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/CapsExtensionProvider.java +new file mode 100644 +index 0000000..d8098a1 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/CapsExtensionProvider.java +@@ -0,0 +1,78 @@ ++/* ++ * Copyright 2009 Jonas Ã…dahl. ++ * Copyright 2011 Florian Schmaus ++ * ++ * All rights reserved. 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.smackx.provider; ++ ++import java.io.IOException; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.jivesoftware.smackx.packet.CapsExtension; ++ ++import org.xmlpull.v1.XmlPullParser; ++import org.xmlpull.v1.XmlPullParserException; ++ ++public class CapsExtensionProvider implements PacketExtensionProvider { ++ public PacketExtension parseExtension(XmlPullParser parser) throws XmlPullParserException, IOException { ++// Original implementation by jonas ++// boolean done = false; ++// int startDepth = parser.getDepth(); ++// ++// String hash = parser.getAttributeValue(null, "hash"); ++// String node = parser.getAttributeValue(null, "node"); ++// String ver = parser.getAttributeValue(null, "ver"); ++// ++// // Make the parser ++// while (true) { ++// int eventType = parser.next(); ++// ++// if (eventType == XmlPullParser.END_TAG && ++// parser.getDepth() == startDepth) ++// break; ++// } ++// ++// if (hash != null && node != null && ver != null) { ++// return new CapsExtension(node, ver, hash); ++// } ++// else { ++// //throw new XMPPException("Malformed caps element."); ++// // Malformed, ignore it ++// return null; ++// } ++ ++ boolean done = false; ++ String hash = null; ++ String version = null; ++ String node = null; ++ while (!done) { ++ if (parser.getEventType() == XmlPullParser.START_TAG ++ && parser.getName().equalsIgnoreCase("c")) { ++ hash = parser.getAttributeValue(null, "hash"); ++ version = parser.getAttributeValue(null, "ver"); ++ node = parser.getAttributeValue(null, "node"); ++ } ++ ++ if (parser.getEventType() == XmlPullParser.END_TAG ++ && parser.getName().equalsIgnoreCase("c")) { ++ done = true; ++ } else { ++ parser.next(); ++ } ++ } ++ return new CapsExtension(node, version, hash); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DataFormProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DataFormProvider.java +new file mode 100644 +index 0000000..90794f4 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DataFormProvider.java +@@ -0,0 +1,160 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.provider; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.jivesoftware.smackx.FormField; ++import org.jivesoftware.smackx.packet.DataForm; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++/** ++ * The DataFormProvider parses DataForm packets. ++ * ++ * @author Gaston Dombiak ++ */ ++public class DataFormProvider implements PacketExtensionProvider { ++ ++ /** ++ * Creates a new DataFormProvider. ++ * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor ++ */ ++ public DataFormProvider() { ++ } ++ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ StringBuilder buffer = null; ++ DataForm dataForm = new DataForm(parser.getAttributeValue("", "type")); ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("instructions")) { ++ dataForm.addInstruction(parser.nextText()); ++ } ++ else if (parser.getName().equals("title")) { ++ dataForm.setTitle(parser.nextText()); ++ } ++ else if (parser.getName().equals("field")) { ++ dataForm.addField(parseField(parser)); ++ } ++ else if (parser.getName().equals("item")) { ++ dataForm.addItem(parseItem(parser)); ++ } ++ else if (parser.getName().equals("reported")) { ++ dataForm.setReportedData(parseReported(parser)); ++ } ++ } else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals(dataForm.getElementName())) { ++ done = true; ++ } ++ } ++ } ++ return dataForm; ++ } ++ ++ private FormField parseField(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ FormField formField = new FormField(parser.getAttributeValue("", "var")); ++ formField.setLabel(parser.getAttributeValue("", "label")); ++ formField.setType(parser.getAttributeValue("", "type")); ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("desc")) { ++ formField.setDescription(parser.nextText()); ++ } ++ else if (parser.getName().equals("value")) { ++ formField.addValue(parser.nextText()); ++ } ++ else if (parser.getName().equals("required")) { ++ formField.setRequired(true); ++ } ++ else if (parser.getName().equals("option")) { ++ formField.addOption(parseOption(parser)); ++ } ++ } else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("field")) { ++ done = true; ++ } ++ } ++ } ++ return formField; ++ } ++ ++ private DataForm.Item parseItem(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ List fields = new ArrayList(); ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("field")) { ++ fields.add(parseField(parser)); ++ } ++ } else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("item")) { ++ done = true; ++ } ++ } ++ } ++ return new DataForm.Item(fields); ++ } ++ ++ private DataForm.ReportedData parseReported(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ List fields = new ArrayList(); ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("field")) { ++ fields.add(parseField(parser)); ++ } ++ } else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("reported")) { ++ done = true; ++ } ++ } ++ } ++ return new DataForm.ReportedData(fields); ++ } ++ ++ private FormField.Option parseOption(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ FormField.Option option = null; ++ String label = parser.getAttributeValue("", "label"); ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("value")) { ++ option = new FormField.Option(label, parser.nextText()); ++ } ++ } else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("option")) { ++ done = true; ++ } ++ } ++ } ++ return option; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DelayInfoProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DelayInfoProvider.java +new file mode 100644 +index 0000000..6fa52b7 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DelayInfoProvider.java +@@ -0,0 +1,42 @@ ++/** ++ * All rights reserved. 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.smackx.provider; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.packet.DelayInfo; ++import org.jivesoftware.smackx.packet.DelayInformation; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * This provider simply creates a {@link DelayInfo} decorator for the {@link DelayInformation} that ++ * is returned by the superclass. This allows the new code using ++ * Delay Information XEP-0203 to be ++ * backward compatible with XEP-0091. ++ * ++ *

      This provider must be registered in the smack.properties file for the element ++ * delay with namespace urn:xmpp:delay

      ++ * ++ * @author Robin Collier ++ */ ++public class DelayInfoProvider extends DelayInformationProvider ++{ ++ ++ @Override ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception ++ { ++ return new DelayInfo((DelayInformation)super.parseExtension(parser)); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DelayInformationProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DelayInformationProvider.java +new file mode 100644 +index 0000000..5dc0ee7 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DelayInformationProvider.java +@@ -0,0 +1,205 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.provider; ++ ++import java.text.DateFormat; ++import java.text.ParseException; ++import java.text.SimpleDateFormat; ++import java.util.ArrayList; ++import java.util.Calendar; ++import java.util.Collections; ++import java.util.Comparator; ++import java.util.Date; ++import java.util.GregorianCalendar; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++import java.util.TimeZone; ++ ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.jivesoftware.smack.util.StringUtils; ++import org.jivesoftware.smackx.packet.DelayInformation; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * The DelayInformationProvider parses DelayInformation packets. ++ * ++ * @author Gaston Dombiak ++ * @author Henning Staib ++ */ ++public class DelayInformationProvider implements PacketExtensionProvider { ++ ++ /* ++ * Date format used to parse dates in the XEP-0091 format but missing leading ++ * zeros for month and day. ++ */ ++ private static final SimpleDateFormat XEP_0091_UTC_FALLBACK_FORMAT = new SimpleDateFormat( ++ "yyyyMd'T'HH:mm:ss"); ++ static { ++ XEP_0091_UTC_FALLBACK_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); ++ } ++ ++ /* ++ * Date format used to parse dates in the XEP-0082 format but missing milliseconds. ++ */ ++ private static final SimpleDateFormat XEP_0082_UTC_FORMAT_WITHOUT_MILLIS = new SimpleDateFormat( ++ "yyyy-MM-dd'T'HH:mm:ss'Z'"); ++ static { ++ XEP_0082_UTC_FORMAT_WITHOUT_MILLIS.setTimeZone(TimeZone.getTimeZone("UTC")); ++ } ++ ++ /* ++ * Maps a regular expression for a date format to the date format parser. ++ */ ++ private static Map formats = new HashMap(); ++ static { ++ formats.put("^\\d+T\\d+:\\d+:\\d+$", DelayInformation.XEP_0091_UTC_FORMAT); ++ formats.put("^\\d+-\\d+-\\d+T\\d+:\\d+:\\d+\\.\\d+Z$", StringUtils.XEP_0082_UTC_FORMAT); ++ formats.put("^\\d+-\\d+-\\d+T\\d+:\\d+:\\d+Z$", XEP_0082_UTC_FORMAT_WITHOUT_MILLIS); ++ } ++ ++ /** ++ * Creates a new DeliveryInformationProvider. ProviderManager requires that ++ * every PacketExtensionProvider has a public, no-argument constructor ++ */ ++ public DelayInformationProvider() { ++ } ++ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ String stampString = (parser.getAttributeValue("", "stamp")); ++ Date stamp = null; ++ DateFormat format = null; ++ ++ for (String regexp : formats.keySet()) { ++ if (stampString.matches(regexp)) { ++ try { ++ format = formats.get(regexp); ++ synchronized (format) { ++ stamp = format.parse(stampString); ++ } ++ } ++ catch (ParseException e) { ++ // do nothing, format is still set ++ } ++ ++ // break because only one regexp can match ++ break; ++ } ++ } ++ ++ /* ++ * if date is in XEP-0091 format handle ambiguous dates missing the ++ * leading zero in month and day ++ */ ++ if (format == DelayInformation.XEP_0091_UTC_FORMAT ++ && stampString.split("T")[0].length() < 8) { ++ stamp = handleDateWithMissingLeadingZeros(stampString); ++ } ++ ++ /* ++ * if date could not be parsed but XML is valid, don't shutdown ++ * connection by throwing an exception instead set timestamp to current ++ * time ++ */ ++ if (stamp == null) { ++ stamp = new Date(); ++ } ++ ++ DelayInformation delayInformation = new DelayInformation(stamp); ++ delayInformation.setFrom(parser.getAttributeValue("", "from")); ++ String reason = parser.nextText(); ++ ++ /* ++ * parser.nextText() returns empty string if there is no reason. ++ * DelayInformation API specifies that null should be returned in that ++ * case. ++ */ ++ reason = "".equals(reason) ? null : reason; ++ delayInformation.setReason(reason); ++ ++ return delayInformation; ++ } ++ ++ /** ++ * Parses the given date string in different ways and returns the date that ++ * lies in the past and/or is nearest to the current date-time. ++ * ++ * @param stampString date in string representation ++ * @return the parsed date ++ */ ++ private Date handleDateWithMissingLeadingZeros(String stampString) { ++ Calendar now = new GregorianCalendar(); ++ Calendar xep91 = null; ++ Calendar xep91Fallback = null; ++ ++ xep91 = parseXEP91Date(stampString, DelayInformation.XEP_0091_UTC_FORMAT); ++ xep91Fallback = parseXEP91Date(stampString, XEP_0091_UTC_FALLBACK_FORMAT); ++ ++ List dates = filterDatesBefore(now, xep91, xep91Fallback); ++ ++ if (!dates.isEmpty()) { ++ return determineNearestDate(now, dates).getTime(); ++ } ++ return null; ++ } ++ ++ private Calendar parseXEP91Date(String stampString, DateFormat dateFormat) { ++ try { ++ synchronized (dateFormat) { ++ dateFormat.parse(stampString); ++ return dateFormat.getCalendar(); ++ } ++ } ++ catch (ParseException e) { ++ return null; ++ } ++ } ++ ++ private List filterDatesBefore(Calendar now, Calendar... dates) { ++ List result = new ArrayList(); ++ ++ for (Calendar calendar : dates) { ++ if (calendar != null && calendar.before(now)) { ++ result.add(calendar); ++ } ++ } ++ ++ return result; ++ } ++ ++ private Calendar determineNearestDate(final Calendar now, List dates) { ++ ++ Collections.sort(dates, new Comparator() { ++ ++ public int compare(Calendar o1, Calendar o2) { ++ Long diff1 = new Long(now.getTimeInMillis() - o1.getTimeInMillis()); ++ Long diff2 = new Long(now.getTimeInMillis() - o2.getTimeInMillis()); ++ return diff1.compareTo(diff2); ++ } ++ ++ }); ++ ++ return dates.get(0); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DeliveryReceiptProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DeliveryReceiptProvider.java +new file mode 100644 +index 0000000..36ce287 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DeliveryReceiptProvider.java +@@ -0,0 +1,40 @@ ++/** ++ * All rights reserved. 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.smackx.provider; ++ ++import java.util.List; ++import java.util.Map; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.packet.DeliveryReceipt; ++import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * The DeliveryReceiptProvider parses DeliveryReceipt packets. ++ * ++ * @author Georg Lukas ++ */ ++public class DeliveryReceiptProvider extends EmbeddedExtensionProvider ++{ ++ ++ @Override ++ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, ++ Map attributeMap, List content) ++ { ++ return new DeliveryReceipt(attributeMap.get("id")); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DiscoverInfoProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DiscoverInfoProvider.java +new file mode 100644 +index 0000000..09defda +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DiscoverInfoProvider.java +@@ -0,0 +1,83 @@ ++/** ++ * $RCSfile$ ++ * $Revision: 7071 $ ++ * $Date: 2007-02-12 08:59:05 +0800 (Mon, 12 Feb 2007) $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.provider; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smack.util.PacketParserUtils; ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++* The DiscoverInfoProvider parses Service Discovery information packets. ++* ++* @author Gaston Dombiak ++*/ ++public class DiscoverInfoProvider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ DiscoverInfo discoverInfo = new DiscoverInfo(); ++ boolean done = false; ++ DiscoverInfo.Feature feature = null; ++ DiscoverInfo.Identity identity = null; ++ String category = ""; ++ String name = ""; ++ String type = ""; ++ String variable = ""; ++ discoverInfo.setNode(parser.getAttributeValue("", "node")); ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("identity")) { ++ // Initialize the variables from the parsed XML ++ category = parser.getAttributeValue("", "category"); ++ name = parser.getAttributeValue("", "name"); ++ type = parser.getAttributeValue("", "type"); ++ } ++ else if (parser.getName().equals("feature")) { ++ // Initialize the variables from the parsed XML ++ variable = parser.getAttributeValue("", "var"); ++ } ++ // Otherwise, it must be a packet extension. ++ else { ++ discoverInfo.addExtension(PacketParserUtils.parsePacketExtension(parser ++ .getName(), parser.getNamespace(), parser)); ++ } ++ } else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("identity")) { ++ // Create a new identity and add it to the discovered info. ++ identity = new DiscoverInfo.Identity(category, name); ++ identity.setType(type); ++ discoverInfo.addIdentity(identity); ++ } ++ if (parser.getName().equals("feature")) { ++ // Create a new feature and add it to the discovered info. ++ discoverInfo.addFeature(variable); ++ } ++ if (parser.getName().equals("query")) { ++ done = true; ++ } ++ } ++ } ++ ++ return discoverInfo; ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DiscoverItemsProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DiscoverItemsProvider.java +new file mode 100644 +index 0000000..fcbe25f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DiscoverItemsProvider.java +@@ -0,0 +1,69 @@ ++/** ++ * $RCSfile$ ++ * $Revision: 7071 $ ++ * $Date: 2007-02-12 08:59:05 +0800 (Mon, 12 Feb 2007) $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.provider; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smackx.packet.*; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++* The DiscoverInfoProvider parses Service Discovery items packets. ++* ++* @author Gaston Dombiak ++*/ ++public class DiscoverItemsProvider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ DiscoverItems discoverItems = new DiscoverItems(); ++ boolean done = false; ++ DiscoverItems.Item item; ++ String jid = ""; ++ String name = ""; ++ String action = ""; ++ String node = ""; ++ discoverItems.setNode(parser.getAttributeValue("", "node")); ++ while (!done) { ++ int eventType = parser.next(); ++ ++ if (eventType == XmlPullParser.START_TAG && "item".equals(parser.getName())) { ++ // Initialize the variables from the parsed XML ++ jid = parser.getAttributeValue("", "jid"); ++ name = parser.getAttributeValue("", "name"); ++ node = parser.getAttributeValue("", "node"); ++ action = parser.getAttributeValue("", "action"); ++ } ++ else if (eventType == XmlPullParser.END_TAG && "item".equals(parser.getName())) { ++ // Create a new Item and add it to DiscoverItems. ++ item = new DiscoverItems.Item(jid); ++ item.setName(name); ++ item.setNode(node); ++ item.setAction(action); ++ discoverItems.addItem(item); ++ } ++ else if (eventType == XmlPullParser.END_TAG && "query".equals(parser.getName())) { ++ done = true; ++ } ++ } ++ ++ return discoverItems; ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/EmbeddedExtensionProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/EmbeddedExtensionProvider.java +new file mode 100644 +index 0000000..3d5ceb4 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/EmbeddedExtensionProvider.java +@@ -0,0 +1,111 @@ ++/** ++ * All rights reserved. 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.smackx.provider; ++ ++import java.util.ArrayList; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.jivesoftware.smack.util.PacketParserUtils; ++import org.jivesoftware.smackx.pubsub.provider.ItemProvider; ++import org.jivesoftware.smackx.pubsub.provider.ItemsProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * ++ * This class simplifies parsing of embedded elements by using the ++ * Template Method Pattern. ++ * After extracting the current element attributes and content of any child elements, the template method ++ * ({@link #createReturnExtension(String, String, Map, List)} is called. Subclasses ++ * then override this method to create the specific return type. ++ * ++ *

      To use this class, you simply register your subclasses as extension providers in the ++ * smack.properties file. Then they will be automatically picked up and used to parse ++ * any child elements. ++ * ++ *

      ++ * For example, given the following message
      ++ * 
      ++ * <message from='pubsub.shakespeare.lit' to='francisco@denmark.lit' id='foo>
      ++ *    <event xmlns='http://jabber.org/protocol/pubsub#event>
      ++ *       <items node='princely_musings'>
      ++ *          <item id='asdjkwei3i34234n356'>
      ++ *             <entry xmlns='http://www.w3.org/2005/Atom'>
      ++ *                <title>Soliloquy</title>
      ++ *                <link rel='alternative' type='text/html'/>
      ++ *                <id>tag:denmark.lit,2003:entry-32397</id>
      ++ *             </entry>
      ++ *          </item>
      ++ *       </items>
      ++ *    </event>
      ++ * </message>
      ++ * 
      ++ * I would have a classes
      ++ * {@link ItemsProvider} extends {@link EmbeddedExtensionProvider}
      ++ * {@link ItemProvider} extends {@link EmbeddedExtensionProvider}
      ++ * and
      ++ * AtomProvider extends {@link PacketExtensionProvider}
      ++ * 
      ++ * These classes are then registered in the meta-inf/smack.providers file
      ++ * as follows.
      ++ * 
      ++ *   <extensionProvider>
      ++ *      <elementName>items</elementName>
      ++ *      <namespace>http://jabber.org/protocol/pubsub#event</namespace>
      ++ *      <className>org.jivesoftware.smackx.provider.ItemsEventProvider</className>
      ++ *   </extensionProvider>
      ++ *   <extensionProvider>
      ++ *       <elementName>item</elementName>
      ++ *       <namespace>http://jabber.org/protocol/pubsub#event</namespace>
      ++ *       <className>org.jivesoftware.smackx.provider.ItemProvider</className>
      ++ *   </extensionProvider>
      ++ * 
      ++ * 
      ++ * ++ * @author Robin Collier ++ * ++ * @deprecated This has been moved to {@link org.jivesoftware.smack.provider.EmbeddedExtensionProvider} ++ */ ++abstract public class EmbeddedExtensionProvider implements PacketExtensionProvider ++{ ++ ++ final public PacketExtension parseExtension(XmlPullParser parser) throws Exception ++ { ++ String namespace = parser.getNamespace(); ++ String name = parser.getName(); ++ Map attMap = new HashMap(); ++ ++ for(int i=0; i extensions = new ArrayList(); ++ ++ do ++ { ++ int tag = parser.next(); ++ ++ if (tag == XmlPullParser.START_TAG) ++ extensions.add(PacketParserUtils.parsePacketExtension(parser.getName(), parser.getNamespace(), parser)); ++ } while (!name.equals(parser.getName())); ++ ++ return createReturnExtension(name, namespace, attMap, extensions); ++ } ++ ++ abstract protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/HeaderProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/HeaderProvider.java +new file mode 100644 +index 0000000..7344880 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/HeaderProvider.java +@@ -0,0 +1,44 @@ ++/** ++ * All rights reserved. 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.smackx.provider; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.jivesoftware.smackx.packet.Header; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * Parses the header element as defined in Stanza Headers and Internet Metadata (SHIM). ++ * ++ * @author Robin Collier ++ */ ++public class HeaderProvider implements PacketExtensionProvider ++{ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception ++ { ++ String name = parser.getAttributeValue(null, "name"); ++ String value = null; ++ ++ parser.next(); ++ ++ if (parser.getEventType() == XmlPullParser.TEXT) ++ value = parser.getText(); ++ ++ while(parser.getEventType() != XmlPullParser.END_TAG) ++ parser.next(); ++ ++ return new Header(name, value); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/HeadersProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/HeadersProvider.java +new file mode 100644 +index 0000000..056dd58 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/HeadersProvider.java +@@ -0,0 +1,37 @@ ++/** ++ * All rights reserved. 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.smackx.provider; ++ ++import java.util.Collection; ++import java.util.List; ++import java.util.Map; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.packet.Header; ++import org.jivesoftware.smackx.packet.HeadersExtension; ++ ++/** ++ * Parses the headers element as defined in Stanza Headers and Internet Metadata (SHIM). ++ * ++ * @author Robin Collier ++ */ ++public class HeadersProvider extends EmbeddedExtensionProvider ++{ ++ @Override ++ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) ++ { ++ return new HeadersExtension((Collection
      )content); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MUCAdminProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MUCAdminProvider.java +new file mode 100644 +index 0000000..1072232 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MUCAdminProvider.java +@@ -0,0 +1,81 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.provider; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smackx.packet.MUCAdmin; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * The MUCAdminProvider parses MUCAdmin packets. (@see MUCAdmin) ++ * ++ * @author Gaston Dombiak ++ */ ++public class MUCAdminProvider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ MUCAdmin mucAdmin = new MUCAdmin(); ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("item")) { ++ mucAdmin.addItem(parseItem(parser)); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("query")) { ++ done = true; ++ } ++ } ++ } ++ ++ return mucAdmin; ++ } ++ ++ private MUCAdmin.Item parseItem(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ MUCAdmin.Item item = ++ new MUCAdmin.Item( ++ parser.getAttributeValue("", "affiliation"), ++ parser.getAttributeValue("", "role")); ++ item.setNick(parser.getAttributeValue("", "nick")); ++ item.setJid(parser.getAttributeValue("", "jid")); ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("actor")) { ++ item.setActor(parser.getAttributeValue("", "jid")); ++ } ++ if (parser.getName().equals("reason")) { ++ item.setReason(parser.nextText()); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("item")) { ++ done = true; ++ } ++ } ++ } ++ return item; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MUCOwnerProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MUCOwnerProvider.java +new file mode 100644 +index 0000000..ff3094e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MUCOwnerProvider.java +@@ -0,0 +1,108 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.provider; ++ ++import org.jivesoftware.smack.packet.*; ++import org.jivesoftware.smack.provider.*; ++import org.jivesoftware.smack.util.PacketParserUtils; ++import org.jivesoftware.smackx.packet.MUCOwner; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * The MUCOwnerProvider parses MUCOwner packets. (@see MUCOwner) ++ * ++ * @author Gaston Dombiak ++ */ ++public class MUCOwnerProvider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ MUCOwner mucOwner = new MUCOwner(); ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("item")) { ++ mucOwner.addItem(parseItem(parser)); ++ } ++ else if (parser.getName().equals("destroy")) { ++ mucOwner.setDestroy(parseDestroy(parser)); ++ } ++ // Otherwise, it must be a packet extension. ++ else { ++ mucOwner.addExtension(PacketParserUtils.parsePacketExtension(parser.getName(), ++ parser.getNamespace(), parser)); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("query")) { ++ done = true; ++ } ++ } ++ } ++ ++ return mucOwner; ++ } ++ ++ private MUCOwner.Item parseItem(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ MUCOwner.Item item = new MUCOwner.Item(parser.getAttributeValue("", "affiliation")); ++ item.setNick(parser.getAttributeValue("", "nick")); ++ item.setRole(parser.getAttributeValue("", "role")); ++ item.setJid(parser.getAttributeValue("", "jid")); ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("actor")) { ++ item.setActor(parser.getAttributeValue("", "jid")); ++ } ++ if (parser.getName().equals("reason")) { ++ item.setReason(parser.nextText()); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("item")) { ++ done = true; ++ } ++ } ++ } ++ return item; ++ } ++ ++ private MUCOwner.Destroy parseDestroy(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ MUCOwner.Destroy destroy = new MUCOwner.Destroy(); ++ destroy.setJid(parser.getAttributeValue("", "jid")); ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("reason")) { ++ destroy.setReason(parser.nextText()); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("destroy")) { ++ done = true; ++ } ++ } ++ } ++ return destroy; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MUCUserProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MUCUserProvider.java +new file mode 100644 +index 0000000..5a98af6 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MUCUserProvider.java +@@ -0,0 +1,174 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.provider; ++ ++import org.jivesoftware.smack.packet.*; ++import org.jivesoftware.smack.provider.*; ++import org.jivesoftware.smackx.packet.*; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * The MUCUserProvider parses packets with extended presence information about ++ * roles and affiliations. ++ * ++ * @author Gaston Dombiak ++ */ ++public class MUCUserProvider implements PacketExtensionProvider { ++ ++ /** ++ * Creates a new MUCUserProvider. ++ * ProviderManager requires that every PacketExtensionProvider has a public, no-argument ++ * constructor ++ */ ++ public MUCUserProvider() { ++ } ++ ++ /** ++ * Parses a MUCUser packet (extension sub-packet). ++ * ++ * @param parser the XML parser, positioned at the starting element of the extension. ++ * @return a PacketExtension. ++ * @throws Exception if a parsing error occurs. ++ */ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ MUCUser mucUser = new MUCUser(); ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("invite")) { ++ mucUser.setInvite(parseInvite(parser)); ++ } ++ if (parser.getName().equals("item")) { ++ mucUser.setItem(parseItem(parser)); ++ } ++ if (parser.getName().equals("password")) { ++ mucUser.setPassword(parser.nextText()); ++ } ++ if (parser.getName().equals("status")) { ++ mucUser.setStatus(new MUCUser.Status(parser.getAttributeValue("", "code"))); ++ } ++ if (parser.getName().equals("decline")) { ++ mucUser.setDecline(parseDecline(parser)); ++ } ++ if (parser.getName().equals("destroy")) { ++ mucUser.setDestroy(parseDestroy(parser)); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("x")) { ++ done = true; ++ } ++ } ++ } ++ ++ return mucUser; ++ } ++ ++ private MUCUser.Item parseItem(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ MUCUser.Item item = ++ new MUCUser.Item( ++ parser.getAttributeValue("", "affiliation"), ++ parser.getAttributeValue("", "role")); ++ item.setNick(parser.getAttributeValue("", "nick")); ++ item.setJid(parser.getAttributeValue("", "jid")); ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("actor")) { ++ item.setActor(parser.getAttributeValue("", "jid")); ++ } ++ if (parser.getName().equals("reason")) { ++ item.setReason(parser.nextText()); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("item")) { ++ done = true; ++ } ++ } ++ } ++ return item; ++ } ++ ++ private MUCUser.Invite parseInvite(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ MUCUser.Invite invite = new MUCUser.Invite(); ++ invite.setFrom(parser.getAttributeValue("", "from")); ++ invite.setTo(parser.getAttributeValue("", "to")); ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("reason")) { ++ invite.setReason(parser.nextText()); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("invite")) { ++ done = true; ++ } ++ } ++ } ++ return invite; ++ } ++ ++ private MUCUser.Decline parseDecline(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ MUCUser.Decline decline = new MUCUser.Decline(); ++ decline.setFrom(parser.getAttributeValue("", "from")); ++ decline.setTo(parser.getAttributeValue("", "to")); ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("reason")) { ++ decline.setReason(parser.nextText()); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("decline")) { ++ done = true; ++ } ++ } ++ } ++ return decline; ++ } ++ ++ private MUCUser.Destroy parseDestroy(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ MUCUser.Destroy destroy = new MUCUser.Destroy(); ++ destroy.setJid(parser.getAttributeValue("", "jid")); ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("reason")) { ++ destroy.setReason(parser.nextText()); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("destroy")) { ++ done = true; ++ } ++ } ++ } ++ return destroy; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MessageEventProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MessageEventProvider.java +new file mode 100644 +index 0000000..b631546 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MessageEventProvider.java +@@ -0,0 +1,77 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.provider; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.jivesoftware.smackx.packet.MessageEvent; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * ++ * The MessageEventProvider parses Message Event packets. ++* ++ * @author Gaston Dombiak ++ */ ++public class MessageEventProvider implements PacketExtensionProvider { ++ ++ /** ++ * Creates a new MessageEventProvider. ++ * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor ++ */ ++ public MessageEventProvider() { ++ } ++ ++ /** ++ * Parses a MessageEvent packet (extension sub-packet). ++ * ++ * @param parser the XML parser, positioned at the starting element of the extension. ++ * @return a PacketExtension. ++ * @throws Exception if a parsing error occurs. ++ */ ++ public PacketExtension parseExtension(XmlPullParser parser) ++ throws Exception { ++ MessageEvent messageEvent = new MessageEvent(); ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("id")) ++ messageEvent.setPacketID(parser.nextText()); ++ if (parser.getName().equals(MessageEvent.COMPOSING)) ++ messageEvent.setComposing(true); ++ if (parser.getName().equals(MessageEvent.DELIVERED)) ++ messageEvent.setDelivered(true); ++ if (parser.getName().equals(MessageEvent.DISPLAYED)) ++ messageEvent.setDisplayed(true); ++ if (parser.getName().equals(MessageEvent.OFFLINE)) ++ messageEvent.setOffline(true); ++ } else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("x")) { ++ done = true; ++ } ++ } ++ } ++ ++ return messageEvent; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MultipleAddressesProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MultipleAddressesProvider.java +new file mode 100644 +index 0000000..b51e5e0 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MultipleAddressesProvider.java +@@ -0,0 +1,67 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx.provider; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.jivesoftware.smackx.packet.MultipleAddresses; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * The MultipleAddressesProvider parses {@link MultipleAddresses} packets. ++ * ++ * @author Gaston Dombiak ++ */ ++public class MultipleAddressesProvider implements PacketExtensionProvider { ++ ++ /** ++ * Creates a new MultipleAddressesProvider. ++ * ProviderManager requires that every PacketExtensionProvider has a public, no-argument ++ * constructor. ++ */ ++ public MultipleAddressesProvider() { ++ } ++ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ MultipleAddresses multipleAddresses = new MultipleAddresses(); ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("address")) { ++ String type = parser.getAttributeValue("", "type"); ++ String jid = parser.getAttributeValue("", "jid"); ++ String node = parser.getAttributeValue("", "node"); ++ String desc = parser.getAttributeValue("", "desc"); ++ boolean delivered = "true".equals(parser.getAttributeValue("", "delivered")); ++ String uri = parser.getAttributeValue("", "uri"); ++ // Add the parsed address ++ multipleAddresses.addAddress(type, jid, node, desc, delivered, uri); ++ } ++ } else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals(multipleAddresses.getElementName())) { ++ done = true; ++ } ++ } ++ } ++ return multipleAddresses; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/PEPProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/PEPProvider.java +new file mode 100644 +index 0000000..f33dcde +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/PEPProvider.java +@@ -0,0 +1,93 @@ ++/** ++ * $RCSfile: PEPProvider.java,v $ ++ * $Revision: 1.2 $ ++ * $Date: 2007/11/06 02:05:09 $ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.provider; ++ ++import java.util.HashMap; ++import java.util.Map; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * ++ * The PEPProvider parses incoming PEPEvent packets. ++ * (XEP-163 has a weird asymmetric deal: outbound PEP are + and inbound are + . ++ * The provider only deals with inbound, and so it only deals with . ++ * ++ * Anyhoo... ++ * ++ * The way this works is that PEPxxx classes are generic and providers, and anyone who ++ * wants to publish/receive PEPs, such as , , etc., simply need to extend PEPItem and register (here) ++ * a PacketExtensionProvider that knows how to parse that PEPItem extension. ++ * ++ * @author Jeff Williams ++ */ ++public class PEPProvider implements PacketExtensionProvider { ++ ++ Map nodeParsers = new HashMap(); ++ PacketExtension pepItem; ++ ++ /** ++ * Creates a new PEPProvider. ++ * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor ++ */ ++ public PEPProvider() { ++ } ++ ++ public void registerPEPParserExtension(String node, PacketExtensionProvider pepItemParser) { ++ nodeParsers.put(node, pepItemParser); ++ } ++ ++ /** ++ * Parses a PEPEvent packet and extracts a PEPItem from it. ++ * (There is only one per .) ++ * ++ * @param parser the XML parser, positioned at the starting element of the extension. ++ * @return a PacketExtension. ++ * @throws Exception if a parsing error occurs. ++ */ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("event")) { ++ } else if (parser.getName().equals("items")) { ++ // Figure out the node for this event. ++ String node = parser.getAttributeValue("", "node"); ++ // Get the parser for this kind of node, and if found then parse the node. ++ PacketExtensionProvider nodeParser = nodeParsers.get(node); ++ if (nodeParser != null) { ++ pepItem = nodeParser.parseExtension(parser); ++ } ++ } ++ } else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("event")) { ++ done = true; ++ } ++ } ++ } ++ ++ return pepItem; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/PrivateDataProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/PrivateDataProvider.java +new file mode 100644 +index 0000000..b781a5a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/PrivateDataProvider.java +@@ -0,0 +1,46 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.provider; ++ ++import org.xmlpull.v1.XmlPullParser; ++import org.jivesoftware.smackx.packet.PrivateData; ++ ++/** ++ * An interface for parsing custom private data. Each PrivateDataProvider must ++ * be registered with the PrivateDataManager class for it to be used. Every implementation ++ * of this interface must have a public, no-argument constructor. ++ * ++ * @author Matt Tucker ++ */ ++public interface PrivateDataProvider { ++ ++ /** ++ * Parse the private data sub-document and create a PrivateData instance. At the ++ * beginning of the method call, the xml parser will be positioned at the opening ++ * tag of the private data child element. At the end of the method call, the parser ++ * must be positioned on the closing tag of the child element. ++ * ++ * @param parser an XML parser. ++ * @return a new PrivateData instance. ++ * @throws Exception if an error occurs parsing the XML. ++ */ ++ public PrivateData parsePrivateData(XmlPullParser parser) throws Exception; ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/RosterExchangeProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/RosterExchangeProvider.java +new file mode 100644 +index 0000000..ab8a02f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/RosterExchangeProvider.java +@@ -0,0 +1,90 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.provider; ++ ++import java.util.ArrayList; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.jivesoftware.smackx.*; ++import org.jivesoftware.smackx.packet.*; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * ++ * The RosterExchangeProvider parses RosterExchange packets. ++ * ++ * @author Gaston Dombiak ++ */ ++public class RosterExchangeProvider implements PacketExtensionProvider { ++ ++ /** ++ * Creates a new RosterExchangeProvider. ++ * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor ++ */ ++ public RosterExchangeProvider() { ++ } ++ ++ /** ++ * Parses a RosterExchange packet (extension sub-packet). ++ * ++ * @param parser the XML parser, positioned at the starting element of the extension. ++ * @return a PacketExtension. ++ * @throws Exception if a parsing error occurs. ++ */ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ ++ RosterExchange rosterExchange = new RosterExchange(); ++ boolean done = false; ++ RemoteRosterEntry remoteRosterEntry = null; ++ String jid = ""; ++ String name = ""; ++ ArrayList groupsName = new ArrayList(); ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("item")) { ++ // Reset this variable since they are optional for each item ++ groupsName = new ArrayList(); ++ // Initialize the variables from the parsed XML ++ jid = parser.getAttributeValue("", "jid"); ++ name = parser.getAttributeValue("", "name"); ++ } ++ if (parser.getName().equals("group")) { ++ groupsName.add(parser.nextText()); ++ } ++ } else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("item")) { ++ // Create packet. ++ remoteRosterEntry = new RemoteRosterEntry(jid, name, (String[]) groupsName.toArray(new String[groupsName.size()])); ++ rosterExchange.addRosterEntry(remoteRosterEntry); ++ } ++ if (parser.getName().equals("x")) { ++ done = true; ++ } ++ } ++ } ++ ++ return rosterExchange; ++ ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/StreamInitiationProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/StreamInitiationProvider.java +new file mode 100644 +index 0000000..eb0c66c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/StreamInitiationProvider.java +@@ -0,0 +1,125 @@ ++/** ++ * $RCSfile$ ++ * $Revision: $ ++ * $Date: $ ++ * ++ * Copyright 2003-2006 Jive Software. ++ * ++ * All rights reserved. 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.smackx.provider; ++ ++import java.text.ParseException; ++import java.util.Date; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smack.util.StringUtils; ++import org.jivesoftware.smackx.packet.DataForm; ++import org.jivesoftware.smackx.packet.StreamInitiation; ++import org.jivesoftware.smackx.packet.StreamInitiation.File; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * The StreamInitiationProvider parses StreamInitiation packets. ++ * ++ * @author Alexander Wenckus ++ * ++ */ ++public class StreamInitiationProvider implements IQProvider { ++ ++ public IQ parseIQ(final XmlPullParser parser) throws Exception { ++ boolean done = false; ++ ++ // si ++ String id = parser.getAttributeValue("", "id"); ++ String mimeType = parser.getAttributeValue("", "mime-type"); ++ ++ StreamInitiation initiation = new StreamInitiation(); ++ ++ // file ++ String name = null; ++ String size = null; ++ String hash = null; ++ String date = null; ++ String desc = null; ++ boolean isRanged = false; ++ ++ // feature ++ DataForm form = null; ++ DataFormProvider dataFormProvider = new DataFormProvider(); ++ ++ int eventType; ++ String elementName; ++ String namespace; ++ while (!done) { ++ eventType = parser.next(); ++ elementName = parser.getName(); ++ namespace = parser.getNamespace(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (elementName.equals("file")) { ++ name = parser.getAttributeValue("", "name"); ++ size = parser.getAttributeValue("", "size"); ++ hash = parser.getAttributeValue("", "hash"); ++ date = parser.getAttributeValue("", "date"); ++ } else if (elementName.equals("desc")) { ++ desc = parser.nextText(); ++ } else if (elementName.equals("range")) { ++ isRanged = true; ++ } else if (elementName.equals("x") ++ && namespace.equals("jabber:x:data")) { ++ form = (DataForm) dataFormProvider.parseExtension(parser); ++ } ++ } else if (eventType == XmlPullParser.END_TAG) { ++ if (elementName.equals("si")) { ++ done = true; ++ } else if (elementName.equals("file")) { ++ long fileSize = 0; ++ if(size != null && size.trim().length() !=0){ ++ try { ++ fileSize = Long.parseLong(size); ++ } ++ catch (NumberFormatException e) { ++ e.printStackTrace(); ++ } ++ } ++ ++ Date fileDate = new Date(); ++ if (date != null) { ++ try { ++ fileDate = StringUtils.parseXEP0082Date(date); ++ } catch (ParseException e) { ++ // couldn't parse date, use current date-time ++ } ++ } ++ ++ File file = new File(name, fileSize); ++ file.setHash(hash); ++ file.setDate(fileDate); ++ file.setDesc(desc); ++ file.setRanged(isRanged); ++ initiation.setFile(file); ++ } ++ } ++ } ++ ++ initiation.setSesssionID(id); ++ initiation.setMimeType(mimeType); ++ ++ initiation.setFeatureNegotiationForm(form); ++ ++ return initiation; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/VCardProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/VCardProvider.java +new file mode 100644 +index 0000000..b52c580 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/VCardProvider.java +@@ -0,0 +1,258 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.provider; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smack.util.StringUtils; ++import org.jivesoftware.smackx.packet.VCard; ++import org.w3c.dom.*; ++import org.xmlpull.v1.XmlPullParser; ++import org.xmlpull.v1.XmlPullParserException; ++ ++import javax.xml.parsers.DocumentBuilder; ++import javax.xml.parsers.DocumentBuilderFactory; ++import java.io.ByteArrayInputStream; ++import java.io.IOException; ++import java.util.ArrayList; ++import java.util.List; ++ ++/** ++ * vCard provider. ++ * ++ * @author Gaston Dombiak ++ * @author Derek DeMoro ++ */ ++public class VCardProvider implements IQProvider { ++ ++ private static final String PREFERRED_ENCODING = "UTF-8"; ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ final StringBuilder sb = new StringBuilder(); ++ try { ++ int event = parser.getEventType(); ++ // get the content ++ while (true) { ++ switch (event) { ++ case XmlPullParser.TEXT: ++ // We must re-escape the xml so that the DOM won't throw an exception ++ sb.append(StringUtils.escapeForXML(parser.getText())); ++ break; ++ case XmlPullParser.START_TAG: ++ sb.append('<').append(parser.getName()).append('>'); ++ break; ++ case XmlPullParser.END_TAG: ++ sb.append("'); ++ break; ++ default: ++ } ++ ++ if (event == XmlPullParser.END_TAG && "vCard".equals(parser.getName())) break; ++ ++ event = parser.next(); ++ } ++ } ++ catch (XmlPullParserException e) { ++ e.printStackTrace(); ++ } ++ catch (IOException e) { ++ e.printStackTrace(); ++ } ++ ++ String xmlText = sb.toString(); ++ return createVCardFromXML(xmlText); ++ } ++ ++ /** ++ * Builds a users vCard from xml file. ++ * ++ * @param xml the xml representing a users vCard. ++ * @return the VCard. ++ * @throws Exception if an exception occurs. ++ */ ++ public static VCard createVCardFromXML(String xml) throws Exception { ++ VCard vCard = new VCard(); ++ ++ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); ++ DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); ++ Document document = documentBuilder.parse( ++ new ByteArrayInputStream(xml.getBytes(PREFERRED_ENCODING))); ++ ++ new VCardReader(vCard, document).initializeFields(); ++ return vCard; ++ } ++ ++ private static class VCardReader { ++ ++ private final VCard vCard; ++ private final Document document; ++ ++ VCardReader(VCard vCard, Document document) { ++ this.vCard = vCard; ++ this.document = document; ++ } ++ ++ public void initializeFields() { ++ vCard.setFirstName(getTagContents("GIVEN")); ++ vCard.setLastName(getTagContents("FAMILY")); ++ vCard.setMiddleName(getTagContents("MIDDLE")); ++ vCard.setEncodedImage(getTagContents("BINVAL")); ++ ++ setupEmails(); ++ ++ vCard.setOrganization(getTagContents("ORGNAME")); ++ vCard.setOrganizationUnit(getTagContents("ORGUNIT")); ++ ++ setupSimpleFields(); ++ ++ setupPhones(); ++ setupAddresses(); ++ } ++ ++ private void setupEmails() { ++ NodeList nodes = document.getElementsByTagName("USERID"); ++ if (nodes == null) return; ++ for (int i = 0; i < nodes.getLength(); i++) { ++ Element element = (Element) nodes.item(i); ++ if ("WORK".equals(element.getParentNode().getFirstChild().getNodeName())) { ++ vCard.setEmailWork(getTextContent(element)); ++ } ++ else { ++ vCard.setEmailHome(getTextContent(element)); ++ } ++ } ++ } ++ ++ private void setupPhones() { ++ NodeList allPhones = document.getElementsByTagName("TEL"); ++ if (allPhones == null) return; ++ for (int i = 0; i < allPhones.getLength(); i++) { ++ NodeList nodes = allPhones.item(i).getChildNodes(); ++ String type = null; ++ String code = null; ++ String value = null; ++ for (int j = 0; j < nodes.getLength(); j++) { ++ Node node = nodes.item(j); ++ if (node.getNodeType() != Node.ELEMENT_NODE) continue; ++ String nodeName = node.getNodeName(); ++ if ("NUMBER".equals(nodeName)) { ++ value = getTextContent(node); ++ } ++ else if (isWorkHome(nodeName)) { ++ type = nodeName; ++ } ++ else { ++ code = nodeName; ++ } ++ } ++ if (code == null || value == null) continue; ++ if ("HOME".equals(type)) { ++ vCard.setPhoneHome(code, value); ++ } ++ else { // By default, setup work phone ++ vCard.setPhoneWork(code, value); ++ } ++ } ++ } ++ ++ private boolean isWorkHome(String nodeName) { ++ return "HOME".equals(nodeName) || "WORK".equals(nodeName); ++ } ++ ++ private void setupAddresses() { ++ NodeList allAddresses = document.getElementsByTagName("ADR"); ++ if (allAddresses == null) return; ++ for (int i = 0; i < allAddresses.getLength(); i++) { ++ Element addressNode = (Element) allAddresses.item(i); ++ ++ String type = null; ++ List code = new ArrayList(); ++ List value = new ArrayList(); ++ NodeList childNodes = addressNode.getChildNodes(); ++ for (int j = 0; j < childNodes.getLength(); j++) { ++ Node node = childNodes.item(j); ++ if (node.getNodeType() != Node.ELEMENT_NODE) continue; ++ String nodeName = node.getNodeName(); ++ if (isWorkHome(nodeName)) { ++ type = nodeName; ++ } ++ else { ++ code.add(nodeName); ++ value.add(getTextContent(node)); ++ } ++ } ++ for (int j = 0; j < value.size(); j++) { ++ if ("HOME".equals(type)) { ++ vCard.setAddressFieldHome((String) code.get(j), (String) value.get(j)); ++ } ++ else { // By default, setup work address ++ vCard.setAddressFieldWork((String) code.get(j), (String) value.get(j)); ++ } ++ } ++ } ++ } ++ ++ private String getTagContents(String tag) { ++ NodeList nodes = document.getElementsByTagName(tag); ++ if (nodes != null && nodes.getLength() == 1) { ++ return getTextContent(nodes.item(0)); ++ } ++ return null; ++ } ++ ++ private void setupSimpleFields() { ++ NodeList childNodes = document.getDocumentElement().getChildNodes(); ++ for (int i = 0; i < childNodes.getLength(); i++) { ++ Node node = childNodes.item(i); ++ if (node instanceof Element) { ++ Element element = (Element) node; ++ ++ String field = element.getNodeName(); ++ if (element.getChildNodes().getLength() == 0) { ++ vCard.setField(field, ""); ++ } ++ else if (element.getChildNodes().getLength() == 1 && ++ element.getChildNodes().item(0) instanceof Text) { ++ vCard.setField(field, getTextContent(element)); ++ } ++ } ++ } ++ } ++ ++ private String getTextContent(Node node) { ++ StringBuilder result = new StringBuilder(); ++ appendText(result, node); ++ return result.toString(); ++ } ++ ++ private void appendText(StringBuilder result, Node node) { ++ NodeList childNodes = node.getChildNodes(); ++ for (int i = 0; i < childNodes.getLength(); i++) { ++ Node nd = childNodes.item(i); ++ String nodeValue = nd.getNodeValue(); ++ if (nodeValue != null) { ++ result.append(nodeValue); ++ } ++ appendText(result, nd); ++ } ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/XHTMLExtensionProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/XHTMLExtensionProvider.java +new file mode 100644 +index 0000000..50f437f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/XHTMLExtensionProvider.java +@@ -0,0 +1,94 @@ ++/** ++ * $RCSfile$ ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.provider; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.jivesoftware.smack.util.StringUtils; ++import org.jivesoftware.smackx.packet.XHTMLExtension; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * The XHTMLExtensionProvider parses XHTML packets. ++ * ++ * @author Gaston Dombiak ++ */ ++public class XHTMLExtensionProvider implements PacketExtensionProvider { ++ ++ /** ++ * Creates a new XHTMLExtensionProvider. ++ * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor ++ */ ++ public XHTMLExtensionProvider() { ++ } ++ ++ /** ++ * Parses a XHTMLExtension packet (extension sub-packet). ++ * ++ * @param parser the XML parser, positioned at the starting element of the extension. ++ * @return a PacketExtension. ++ * @throws Exception if a parsing error occurs. ++ */ ++ public PacketExtension parseExtension(XmlPullParser parser) ++ throws Exception { ++ XHTMLExtension xhtmlExtension = new XHTMLExtension(); ++ boolean done = false; ++ StringBuilder buffer = new StringBuilder(); ++ int startDepth = parser.getDepth(); ++ int depth = parser.getDepth(); ++ String lastTag = ""; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("body")) { ++ buffer = new StringBuilder(); ++ depth = parser.getDepth(); ++ } ++ lastTag = parser.getText(); ++ buffer.append(parser.getText()); ++ } else if (eventType == XmlPullParser.TEXT) { ++ if (buffer != null) { ++ // We need to return valid XML so any inner text needs to be re-escaped ++ buffer.append(StringUtils.escapeForXML(parser.getText())); ++ } ++ } else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("body") && parser.getDepth() <= depth) { ++ buffer.append(parser.getText()); ++ xhtmlExtension.addBody(buffer.toString()); ++ } ++ else if (parser.getName().equals(xhtmlExtension.getElementName()) ++ && parser.getDepth() <= startDepth) { ++ done = true; ++ } ++ else { ++ // This is a check for tags that are both a start and end tag like
      ++ // So that they aren't doubled ++ if(lastTag == null || !lastTag.equals(parser.getText())) { ++ buffer.append(parser.getText()); ++ } ++ } ++ } ++ } ++ ++ return xhtmlExtension; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/package.html +new file mode 100644 +index 0000000..962ba63 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/package.html +@@ -0,0 +1 @@ ++Provides pluggable parsing logic for Smack extensions. +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/AccessModel.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/AccessModel.java +new file mode 100644 +index 0000000..c1fa546 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/AccessModel.java +@@ -0,0 +1,38 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++/** ++ * This enumeration represents the access models for the pubsub node ++ * as defined in the pubsub specification section 16.4.3 ++ * ++ * @author Robin Collier ++ */ ++public enum AccessModel ++{ ++ /** Anyone may subscribe and retrieve items */ ++ open, ++ ++ /** Subscription request must be approved and only subscribers may retrieve items */ ++ authorize, ++ ++ /** Anyone with a presence subscription of both or from may subscribe and retrieve items */ ++ presence, ++ ++ /** Anyone in the specified roster group(s) may subscribe and retrieve items */ ++ roster, ++ ++ /** Only those on a whitelist may subscribe and retrieve items */ ++ whitelist; ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Affiliation.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Affiliation.java +new file mode 100644 +index 0000000..d55534d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Affiliation.java +@@ -0,0 +1,111 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++/** ++ * Represents a affiliation between a user and a node, where the {@link #type} defines ++ * the type of affiliation. ++ * ++ * Affiliations are retrieved from the {@link PubSubManager#getAffiliations()} method, which ++ * gets affiliations for the calling user, based on the identity that is associated with ++ * the {@link Connection}. ++ * ++ * @author Robin Collier ++ */ ++public class Affiliation implements PacketExtension ++{ ++ protected String jid; ++ protected String node; ++ protected Type type; ++ ++ public enum Type ++ { ++ member, none, outcast, owner, publisher ++ } ++ ++ /** ++ * Constructs an affiliation. ++ * ++ * @param jid The JID with affiliation. ++ * @param affiliation The type of affiliation. ++ */ ++ public Affiliation(String jid, Type affiliation) ++ { ++ this(jid, null, affiliation); ++ } ++ ++ /** ++ * Constructs an affiliation. ++ * ++ * @param jid The JID with affiliation. ++ * @param node The node with affiliation. ++ * @param affiliation The type of affiliation. ++ */ ++ public Affiliation(String jid, String node, Type affiliation) ++ { ++ this.jid = jid; ++ this.node = node; ++ type = affiliation; ++ } ++ ++ public String getJid() ++ { ++ return jid; ++ } ++ ++ public String getNode() ++ { ++ return node; ++ } ++ ++ public Type getType() ++ { ++ return type; ++ } ++ ++ public String getElementName() ++ { ++ return "affiliation"; ++ } ++ ++ public String getNamespace() ++ { ++ return null; ++ } ++ ++ public String toXML() ++ { ++ StringBuilder builder = new StringBuilder("<"); ++ builder.append(getElementName()); ++ if (node != null) ++ appendAttribute(builder, "node", node); ++ appendAttribute(builder, "jid", jid); ++ appendAttribute(builder, "affiliation", type.toString()); ++ ++ builder.append("/>"); ++ return builder.toString(); ++ } ++ ++ private void appendAttribute(StringBuilder builder, String att, String value) ++ { ++ builder.append(" "); ++ builder.append(att); ++ builder.append("='"); ++ builder.append(value); ++ builder.append("'"); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/AffiliationsExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/AffiliationsExtension.java +new file mode 100644 +index 0000000..563147e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/AffiliationsExtension.java +@@ -0,0 +1,91 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import java.util.Collections; ++import java.util.List; ++ ++/** ++ * Represents the affiliations element of the reply to a request for affiliations. ++ * It is defined in the specification in section 5.7 Retrieve Affiliations. ++ * ++ * @author Robin Collier ++ */ ++public class AffiliationsExtension extends NodeExtension ++{ ++ protected List items = Collections.EMPTY_LIST; ++ ++ public AffiliationsExtension() ++ { ++ super(PubSubElementType.AFFILIATIONS); ++ } ++ ++ public AffiliationsExtension(List affiliationList) ++ { ++ super(PubSubElementType.AFFILIATIONS); ++ ++ if (affiliationList != null) ++ items = affiliationList; ++ } ++ ++ /** ++ * Affiliations for the specified node. ++ * ++ * @param nodeId ++ * @param subList ++ */ ++ public AffiliationsExtension(String nodeId, List affiliationList) ++ { ++ super(PubSubElementType.AFFILIATIONS, nodeId); ++ ++ if (affiliationList != null) ++ items = affiliationList; ++ } ++ ++ public List getAffiliations() ++ { ++ return items; ++ } ++ ++ @Override ++ public String toXML() ++ { ++ if ((items == null) || (items.size() == 0)) ++ { ++ return super.toXML(); ++ } ++ else ++ { ++ StringBuilder builder = new StringBuilder("<"); ++ builder.append(getElementName()); ++ if (getNode() != null) ++ { ++ builder.append(" node='"); ++ builder.append(getNode()); ++ builder.append("'"); ++ } ++ builder.append(">"); ++ ++ for (Affiliation item : items) ++ { ++ builder.append(item.toXML()); ++ } ++ ++ builder.append(""); ++ return builder.toString(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ChildrenAssociationPolicy.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ChildrenAssociationPolicy.java +new file mode 100644 +index 0000000..933a39e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ChildrenAssociationPolicy.java +@@ -0,0 +1,32 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++/** ++ * This enumeration represents the children association policy for associating leaf nodes ++ * with collection nodes as defined in the pubsub specification section 16.4.3 ++ * ++ * @author Robin Collier ++ */ ++public enum ChildrenAssociationPolicy ++{ ++ /** Anyone may associate leaf nodes with the collection */ ++ all, ++ ++ /** Only collection node owners may associate leaf nodes with the collection. */ ++ owners, ++ ++ /** Only those on a whitelist may associate leaf nodes with the collection. */ ++ whitelist; ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/CollectionNode.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/CollectionNode.java +new file mode 100644 +index 0000000..0300610 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/CollectionNode.java +@@ -0,0 +1,15 @@ ++/* ++ * Created on 2009-07-13 ++ */ ++package org.jivesoftware.smackx.pubsub; ++ ++import org.jivesoftware.smack.Connection; ++ ++public class CollectionNode extends Node ++{ ++ CollectionNode(Connection connection, String nodeId) ++ { ++ super(connection, nodeId); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ConfigurationEvent.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ConfigurationEvent.java +new file mode 100644 +index 0000000..67b8304 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ConfigurationEvent.java +@@ -0,0 +1,56 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import java.util.Arrays; ++import java.util.Collections; ++import java.util.List; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++/** ++ * Represents the configuration element of a pubsub message event which ++ * associates a configuration form to the node which was configured. The form ++ * contains the current node configuration. ++ * ++ * @author Robin Collier ++ */ ++public class ConfigurationEvent extends NodeExtension implements EmbeddedPacketExtension ++{ ++ private ConfigureForm form; ++ ++ public ConfigurationEvent(String nodeId) ++ { ++ super(PubSubElementType.CONFIGURATION, nodeId); ++ } ++ ++ public ConfigurationEvent(String nodeId, ConfigureForm configForm) ++ { ++ super(PubSubElementType.CONFIGURATION, nodeId); ++ form = configForm; ++ } ++ ++ public ConfigureForm getConfiguration() ++ { ++ return form; ++ } ++ ++ public List getExtensions() ++ { ++ if (getConfiguration() == null) ++ return Collections.EMPTY_LIST; ++ else ++ return Arrays.asList(((PacketExtension)getConfiguration().getDataFormToSend())); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ConfigureForm.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ConfigureForm.java +new file mode 100644 +index 0000000..7f1005e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ConfigureForm.java +@@ -0,0 +1,709 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++ ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.FormField; ++import org.jivesoftware.smackx.packet.DataForm; ++ ++/** ++ * A decorator for a {@link Form} to easily enable reading and updating ++ * of node configuration. All operations read or update the underlying {@link DataForm}. ++ * ++ *

      Unlike the {@link Form}.setAnswer(XXX)} methods, which throw an exception if the field does not ++ * exist, all ConfigureForm.setXXX methods will create the field in the wrapped form ++ * if it does not already exist. ++ * ++ * @author Robin Collier ++ */ ++public class ConfigureForm extends Form ++{ ++ /** ++ * Create a decorator from an existing {@link DataForm} that has been ++ * retrieved from parsing a node configuration request. ++ * ++ * @param configDataForm ++ */ ++ public ConfigureForm(DataForm configDataForm) ++ { ++ super(configDataForm); ++ } ++ ++ /** ++ * Create a decorator from an existing {@link Form} for node configuration. ++ * Typically, this can be used to create a decorator for an answer form ++ * by using the result of {@link #createAnswerForm()} as the input parameter. ++ * ++ * @param nodeConfigForm ++ */ ++ public ConfigureForm(Form nodeConfigForm) ++ { ++ super(nodeConfigForm.getDataFormToSend()); ++ } ++ ++ /** ++ * Create a new form for configuring a node. This would typically only be used ++ * when creating and configuring a node at the same time via {@link PubSubManager#createNode(String, Form)}, since ++ * configuration of an existing node is typically accomplished by calling {@link LeafNode#getNodeConfiguration()} and ++ * using the resulting form to create a answer form. See {@link #ConfigureForm(Form)}. ++ * @param formType ++ */ ++ public ConfigureForm(FormType formType) ++ { ++ super(formType.toString()); ++ } ++ ++ /** ++ * Get the currently configured {@link AccessModel}, null if it is not set. ++ * ++ * @return The current {@link AccessModel} ++ */ ++ public AccessModel getAccessModel() ++ { ++ String value = getFieldValue(ConfigureNodeFields.access_model); ++ ++ if (value == null) ++ return null; ++ else ++ return AccessModel.valueOf(value); ++ } ++ ++ /** ++ * Sets the value of access model. ++ * ++ * @param accessModel ++ */ ++ public void setAccessModel(AccessModel accessModel) ++ { ++ addField(ConfigureNodeFields.access_model, FormField.TYPE_LIST_SINGLE); ++ setAnswer(ConfigureNodeFields.access_model.getFieldName(), getListSingle(accessModel.toString())); ++ } ++ ++ /** ++ * Returns the URL of an XSL transformation which can be applied to payloads in order to ++ * generate an appropriate message body element. ++ * ++ * @return URL to an XSL ++ */ ++ public String getBodyXSLT() ++ { ++ return getFieldValue(ConfigureNodeFields.body_xslt); ++ } ++ ++ /** ++ * Set the URL of an XSL transformation which can be applied to payloads in order to ++ * generate an appropriate message body element. ++ * ++ * @param bodyXslt The URL of an XSL ++ */ ++ public void setBodyXSLT(String bodyXslt) ++ { ++ addField(ConfigureNodeFields.body_xslt, FormField.TYPE_TEXT_SINGLE); ++ setAnswer(ConfigureNodeFields.body_xslt.getFieldName(), bodyXslt); ++ } ++ ++ /** ++ * The id's of the child nodes associated with a collection node (both leaf and collection). ++ * ++ * @return Iterator over the list of child nodes. ++ */ ++ public Iterator getChildren() ++ { ++ return getFieldValues(ConfigureNodeFields.children); ++ } ++ ++ /** ++ * Set the list of child node ids that are associated with a collection node. ++ * ++ * @param children ++ */ ++ public void setChildren(List children) ++ { ++ addField(ConfigureNodeFields.children, FormField.TYPE_TEXT_MULTI); ++ setAnswer(ConfigureNodeFields.children.getFieldName(), children); ++ } ++ ++ /** ++ * Returns the policy that determines who may associate children with the node. ++ * ++ * @return The current policy ++ */ ++ public ChildrenAssociationPolicy getChildrenAssociationPolicy() ++ { ++ String value = getFieldValue(ConfigureNodeFields.children_association_policy); ++ ++ if (value == null) ++ return null; ++ else ++ return ChildrenAssociationPolicy.valueOf(value); ++ } ++ ++ /** ++ * Sets the policy that determines who may associate children with the node. ++ * ++ * @param policy The policy being set ++ */ ++ public void setChildrenAssociationPolicy(ChildrenAssociationPolicy policy) ++ { ++ addField(ConfigureNodeFields.children_association_policy, FormField.TYPE_LIST_SINGLE); ++ List values = new ArrayList(1); ++ values.add(policy.toString()); ++ setAnswer(ConfigureNodeFields.children_association_policy.getFieldName(), values); ++ } ++ ++ /** ++ * Iterator of JID's that are on the whitelist that determines who can associate child nodes ++ * with the collection node. This is only relevant if {@link #getChildrenAssociationPolicy()} is set to ++ * {@link ChildrenAssociationPolicy#whitelist}. ++ * ++ * @return Iterator over whitelist ++ */ ++ public Iterator getChildrenAssociationWhitelist() ++ { ++ return getFieldValues(ConfigureNodeFields.children_association_whitelist); ++ } ++ ++ /** ++ * Set the JID's in the whitelist of users that can associate child nodes with the collection ++ * node. This is only relevant if {@link #getChildrenAssociationPolicy()} is set to ++ * {@link ChildrenAssociationPolicy#whitelist}. ++ * ++ * @param whitelist The list of JID's ++ */ ++ public void setChildrenAssociationWhitelist(List whitelist) ++ { ++ addField(ConfigureNodeFields.children_association_whitelist, FormField.TYPE_JID_MULTI); ++ setAnswer(ConfigureNodeFields.children_association_whitelist.getFieldName(), whitelist); ++ } ++ ++ /** ++ * Gets the maximum number of child nodes that can be associated with the collection node. ++ * ++ * @return The maximum number of child nodes ++ */ ++ public int getChildrenMax() ++ { ++ return Integer.parseInt(getFieldValue(ConfigureNodeFields.children_max)); ++ } ++ ++ /** ++ * Set the maximum number of child nodes that can be associated with a collection node. ++ * ++ * @param max The maximum number of child nodes. ++ */ ++ public void setChildrenMax(int max) ++ { ++ addField(ConfigureNodeFields.children_max, FormField.TYPE_TEXT_SINGLE); ++ setAnswer(ConfigureNodeFields.children_max.getFieldName(), max); ++ } ++ ++ /** ++ * Gets the collection node which the node is affiliated with. ++ * ++ * @return The collection node id ++ */ ++ public String getCollection() ++ { ++ return getFieldValue(ConfigureNodeFields.collection); ++ } ++ ++ /** ++ * Sets the collection node which the node is affiliated with. ++ * ++ * @param collection The node id of the collection node ++ */ ++ public void setCollection(String collection) ++ { ++ addField(ConfigureNodeFields.collection, FormField.TYPE_TEXT_SINGLE); ++ setAnswer(ConfigureNodeFields.collection.getFieldName(), collection); ++ } ++ ++ /** ++ * Gets the URL of an XSL transformation which can be applied to the payload ++ * format in order to generate a valid Data Forms result that the client could ++ * display using a generic Data Forms rendering engine. ++ * ++ * @return The URL of an XSL transformation ++ */ ++ public String getDataformXSLT() ++ { ++ return getFieldValue(ConfigureNodeFields.dataform_xslt); ++ } ++ ++ /** ++ * Sets the URL of an XSL transformation which can be applied to the payload ++ * format in order to generate a valid Data Forms result that the client could ++ * display using a generic Data Forms rendering engine. ++ * ++ * @param url The URL of an XSL transformation ++ */ ++ public void setDataformXSLT(String url) ++ { ++ addField(ConfigureNodeFields.dataform_xslt, FormField.TYPE_TEXT_SINGLE); ++ setAnswer(ConfigureNodeFields.dataform_xslt.getFieldName(), url); ++ } ++ ++ /** ++ * Does the node deliver payloads with event notifications. ++ * ++ * @return true if it does, false otherwise ++ */ ++ public boolean isDeliverPayloads() ++ { ++ return parseBoolean(getFieldValue(ConfigureNodeFields.deliver_payloads)); ++ } ++ ++ /** ++ * Sets whether the node will deliver payloads with event notifications. ++ * ++ * @param deliver true if the payload will be delivered, false otherwise ++ */ ++ public void setDeliverPayloads(boolean deliver) ++ { ++ addField(ConfigureNodeFields.deliver_payloads, FormField.TYPE_BOOLEAN); ++ setAnswer(ConfigureNodeFields.deliver_payloads.getFieldName(), deliver); ++ } ++ ++ /** ++ * Determines who should get replies to items ++ * ++ * @return Who should get the reply ++ */ ++ public ItemReply getItemReply() ++ { ++ String value = getFieldValue(ConfigureNodeFields.itemreply); ++ ++ if (value == null) ++ return null; ++ else ++ return ItemReply.valueOf(value); ++ } ++ ++ /** ++ * Sets who should get the replies to items ++ * ++ * @param reply Defines who should get the reply ++ */ ++ public void setItemReply(ItemReply reply) ++ { ++ addField(ConfigureNodeFields.itemreply, FormField.TYPE_LIST_SINGLE); ++ setAnswer(ConfigureNodeFields.itemreply.getFieldName(), getListSingle(reply.toString())); ++ } ++ ++ /** ++ * Gets the maximum number of items to persisted to this node if {@link #isPersistItems()} is ++ * true. ++ * ++ * @return The maximum number of items to persist ++ */ ++ public int getMaxItems() ++ { ++ return Integer.parseInt(getFieldValue(ConfigureNodeFields.max_items)); ++ } ++ ++ /** ++ * Set the maximum number of items to persisted to this node if {@link #isPersistItems()} is ++ * true. ++ * ++ * @param max The maximum number of items to persist ++ */ ++ public void setMaxItems(int max) ++ { ++ addField(ConfigureNodeFields.max_items, FormField.TYPE_TEXT_SINGLE); ++ setAnswer(ConfigureNodeFields.max_items.getFieldName(), max); ++ } ++ ++ /** ++ * Gets the maximum payload size in bytes. ++ * ++ * @return The maximum payload size ++ */ ++ public int getMaxPayloadSize() ++ { ++ return Integer.parseInt(getFieldValue(ConfigureNodeFields.max_payload_size)); ++ } ++ ++ /** ++ * Sets the maximum payload size in bytes ++ * ++ * @param max The maximum payload size ++ */ ++ public void setMaxPayloadSize(int max) ++ { ++ addField(ConfigureNodeFields.max_payload_size, FormField.TYPE_TEXT_SINGLE); ++ setAnswer(ConfigureNodeFields.max_payload_size.getFieldName(), max); ++ } ++ ++ /** ++ * Gets the node type ++ * ++ * @return The node type ++ */ ++ public NodeType getNodeType() ++ { ++ String value = getFieldValue(ConfigureNodeFields.node_type); ++ ++ if (value == null) ++ return null; ++ else ++ return NodeType.valueOf(value); ++ } ++ ++ /** ++ * Sets the node type ++ * ++ * @param type The node type ++ */ ++ public void setNodeType(NodeType type) ++ { ++ addField(ConfigureNodeFields.node_type, FormField.TYPE_LIST_SINGLE); ++ setAnswer(ConfigureNodeFields.node_type.getFieldName(), getListSingle(type.toString())); ++ } ++ ++ /** ++ * Determines if subscribers should be notified when the configuration changes. ++ * ++ * @return true if they should be notified, false otherwise ++ */ ++ public boolean isNotifyConfig() ++ { ++ return parseBoolean(getFieldValue(ConfigureNodeFields.notify_config)); ++ } ++ ++ /** ++ * Sets whether subscribers should be notified when the configuration changes. ++ * ++ * @param notify true if subscribers should be notified, false otherwise ++ */ ++ public void setNotifyConfig(boolean notify) ++ { ++ addField(ConfigureNodeFields.notify_config, FormField.TYPE_BOOLEAN); ++ setAnswer(ConfigureNodeFields.notify_config.getFieldName(), notify); ++ } ++ ++ /** ++ * Determines whether subscribers should be notified when the node is deleted. ++ * ++ * @return true if subscribers should be notified, false otherwise ++ */ ++ public boolean isNotifyDelete() ++ { ++ return parseBoolean(getFieldValue(ConfigureNodeFields.notify_delete)); ++ } ++ ++ /** ++ * Sets whether subscribers should be notified when the node is deleted. ++ * ++ * @param notify true if subscribers should be notified, false otherwise ++ */ ++ public void setNotifyDelete(boolean notify) ++ { ++ addField(ConfigureNodeFields.notify_delete, FormField.TYPE_BOOLEAN); ++ setAnswer(ConfigureNodeFields.notify_delete.getFieldName(), notify); ++ } ++ ++ /** ++ * Determines whether subscribers should be notified when items are deleted ++ * from the node. ++ * ++ * @return true if subscribers should be notified, false otherwise ++ */ ++ public boolean isNotifyRetract() ++ { ++ return parseBoolean(getFieldValue(ConfigureNodeFields.notify_retract)); ++ } ++ ++ /** ++ * Sets whether subscribers should be notified when items are deleted ++ * from the node. ++ * ++ * @param notify true if subscribers should be notified, false otherwise ++ */ ++ public void setNotifyRetract(boolean notify) ++ { ++ addField(ConfigureNodeFields.notify_retract, FormField.TYPE_BOOLEAN); ++ setAnswer(ConfigureNodeFields.notify_retract.getFieldName(), notify); ++ } ++ ++ /** ++ * Determines whether items should be persisted in the node. ++ * ++ * @return true if items are persisted ++ */ ++ public boolean isPersistItems() ++ { ++ return parseBoolean(getFieldValue(ConfigureNodeFields.persist_items)); ++ } ++ ++ /** ++ * Sets whether items should be persisted in the node. ++ * ++ * @param persist true if items should be persisted, false otherwise ++ */ ++ public void setPersistentItems(boolean persist) ++ { ++ addField(ConfigureNodeFields.persist_items, FormField.TYPE_BOOLEAN); ++ setAnswer(ConfigureNodeFields.persist_items.getFieldName(), persist); ++ } ++ ++ /** ++ * Determines whether to deliver notifications to available users only. ++ * ++ * @return true if users must be available ++ */ ++ public boolean isPresenceBasedDelivery() ++ { ++ return parseBoolean(getFieldValue(ConfigureNodeFields.presence_based_delivery)); ++ } ++ ++ /** ++ * Sets whether to deliver notifications to available users only. ++ * ++ * @param presenceBased true if user must be available, false otherwise ++ */ ++ public void setPresenceBasedDelivery(boolean presenceBased) ++ { ++ addField(ConfigureNodeFields.presence_based_delivery, FormField.TYPE_BOOLEAN); ++ setAnswer(ConfigureNodeFields.presence_based_delivery.getFieldName(), presenceBased); ++ } ++ ++ /** ++ * Gets the publishing model for the node, which determines who may publish to it. ++ * ++ * @return The publishing model ++ */ ++ public PublishModel getPublishModel() ++ { ++ String value = getFieldValue(ConfigureNodeFields.publish_model); ++ ++ if (value == null) ++ return null; ++ else ++ return PublishModel.valueOf(value); ++ } ++ ++ /** ++ * Sets the publishing model for the node, which determines who may publish to it. ++ * ++ * @param publish The enum representing the possible options for the publishing model ++ */ ++ public void setPublishModel(PublishModel publish) ++ { ++ addField(ConfigureNodeFields.publish_model, FormField.TYPE_LIST_SINGLE); ++ setAnswer(ConfigureNodeFields.publish_model.getFieldName(), getListSingle(publish.toString())); ++ } ++ ++ /** ++ * Iterator over the multi user chat rooms that are specified as reply rooms. ++ * ++ * @return The reply room JID's ++ */ ++ public Iterator getReplyRoom() ++ { ++ return getFieldValues(ConfigureNodeFields.replyroom); ++ } ++ ++ /** ++ * Sets the multi user chat rooms that are specified as reply rooms. ++ * ++ * @param replyRooms The multi user chat room to use as reply rooms ++ */ ++ public void setReplyRoom(List replyRooms) ++ { ++ addField(ConfigureNodeFields.replyroom, FormField.TYPE_LIST_MULTI); ++ setAnswer(ConfigureNodeFields.replyroom.getFieldName(), replyRooms); ++ } ++ ++ /** ++ * Gets the specific JID's for reply to. ++ * ++ * @return The JID's ++ */ ++ public Iterator getReplyTo() ++ { ++ return getFieldValues(ConfigureNodeFields.replyto); ++ } ++ ++ /** ++ * Sets the specific JID's for reply to. ++ * ++ * @param replyTos The JID's to reply to ++ */ ++ public void setReplyTo(List replyTos) ++ { ++ addField(ConfigureNodeFields.replyto, FormField.TYPE_LIST_MULTI); ++ setAnswer(ConfigureNodeFields.replyto.getFieldName(), replyTos); ++ } ++ ++ /** ++ * Gets the roster groups that are allowed to subscribe and retrieve items. ++ * ++ * @return The roster groups ++ */ ++ public Iterator getRosterGroupsAllowed() ++ { ++ return getFieldValues(ConfigureNodeFields.roster_groups_allowed); ++ } ++ ++ /** ++ * Sets the roster groups that are allowed to subscribe and retrieve items. ++ * ++ * @param groups The roster groups ++ */ ++ public void setRosterGroupsAllowed(List groups) ++ { ++ addField(ConfigureNodeFields.roster_groups_allowed, FormField.TYPE_LIST_MULTI); ++ setAnswer(ConfigureNodeFields.roster_groups_allowed.getFieldName(), groups); ++ } ++ ++ /** ++ * Determines if subscriptions are allowed. ++ * ++ * @return true if subscriptions are allowed, false otherwise ++ */ ++ public boolean isSubscibe() ++ { ++ return parseBoolean(getFieldValue(ConfigureNodeFields.subscribe)); ++ } ++ ++ /** ++ * Sets whether subscriptions are allowed. ++ * ++ * @param subscribe true if they are, false otherwise ++ */ ++ public void setSubscribe(boolean subscribe) ++ { ++ addField(ConfigureNodeFields.subscribe, FormField.TYPE_BOOLEAN); ++ setAnswer(ConfigureNodeFields.subscribe.getFieldName(), subscribe); ++ } ++ ++ /** ++ * Gets the human readable node title. ++ * ++ * @return The node title ++ */ ++ public String getTitle() ++ { ++ return getFieldValue(ConfigureNodeFields.title); ++ } ++ ++ /** ++ * Sets a human readable title for the node. ++ * ++ * @param title The node title ++ */ ++ public void setTitle(String title) ++ { ++ addField(ConfigureNodeFields.title, FormField.TYPE_TEXT_SINGLE); ++ setAnswer(ConfigureNodeFields.title.getFieldName(), title); ++ } ++ ++ /** ++ * The type of node data, usually specified by the namespace of the payload (if any). ++ * ++ * @return The type of node data ++ */ ++ public String getDataType() ++ { ++ return getFieldValue(ConfigureNodeFields.type); ++ } ++ ++ /** ++ * Sets the type of node data, usually specified by the namespace of the payload (if any). ++ * ++ * @param type The type of node data ++ */ ++ public void setDataType(String type) ++ { ++ addField(ConfigureNodeFields.type, FormField.TYPE_TEXT_SINGLE); ++ setAnswer(ConfigureNodeFields.type.getFieldName(), type); ++ } ++ ++ @Override ++ public String toString() ++ { ++ StringBuilder result = new StringBuilder(getClass().getName() + " Content ["); ++ ++ Iterator fields = getFields(); ++ ++ while (fields.hasNext()) ++ { ++ FormField formField = fields.next(); ++ result.append('('); ++ result.append(formField.getVariable()); ++ result.append(':'); ++ ++ Iterator values = formField.getValues(); ++ StringBuilder valuesBuilder = new StringBuilder(); ++ ++ while (values.hasNext()) ++ { ++ if (valuesBuilder.length() > 0) ++ result.append(','); ++ String value = (String)values.next(); ++ valuesBuilder.append(value); ++ } ++ ++ if (valuesBuilder.length() == 0) ++ valuesBuilder.append("NOT SET"); ++ result.append(valuesBuilder); ++ result.append(')'); ++ } ++ result.append(']'); ++ return result.toString(); ++ } ++ ++ static private boolean parseBoolean(String fieldValue) ++ { ++ return ("1".equals(fieldValue) || "true".equals(fieldValue)); ++ } ++ ++ private String getFieldValue(ConfigureNodeFields field) ++ { ++ FormField formField = getField(field.getFieldName()); ++ ++ return formField.getValues().next(); ++ } ++ ++ private Iterator getFieldValues(ConfigureNodeFields field) ++ { ++ FormField formField = getField(field.getFieldName()); ++ ++ return formField.getValues(); ++ } ++ ++ private void addField(ConfigureNodeFields nodeField, String type) ++ { ++ String fieldName = nodeField.getFieldName(); ++ ++ if (getField(fieldName) == null) ++ { ++ FormField field = new FormField(fieldName); ++ field.setType(type); ++ addField(field); ++ } ++ } ++ ++ private List getListSingle(String value) ++ { ++ List list = new ArrayList(1); ++ list.add(value); ++ return list; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ConfigureNodeFields.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ConfigureNodeFields.java +new file mode 100644 +index 0000000..3912483 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ConfigureNodeFields.java +@@ -0,0 +1,218 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import java.net.URL; ++ ++import org.jivesoftware.smackx.Form; ++ ++/** ++ * This enumeration represents all the fields of a node configuration form. This enumeration ++ * is not required when using the {@link ConfigureForm} to configure nodes, but may be helpful ++ * for generic UI's using only a {@link Form} for configuration. ++ * ++ * @author Robin Collier ++ */ ++public enum ConfigureNodeFields ++{ ++ /** ++ * Determines who may subscribe and retrieve items ++ * ++ *

      Value: {@link AccessModel}

      ++ */ ++ access_model, ++ ++ /** ++ * The URL of an XSL transformation which can be applied to ++ * payloads in order to generate an appropriate message ++ * body element ++ * ++ *

      Value: {@link URL}

      ++ */ ++ body_xslt, ++ ++ /** ++ * The collection with which a node is affiliated ++ * ++ *

      Value: String

      ++ */ ++ collection, ++ ++ /** ++ * The URL of an XSL transformation which can be applied to ++ * payload format in order to generate a valid Data Forms result ++ * that the client could display using a generic Data Forms ++ * rendering engine body element. ++ * ++ *

      Value: {@link URL}

      ++ */ ++ dataform_xslt, ++ ++ /** ++ * Whether to deliver payloads with event notifications ++ * ++ *

      Value: boolean

      ++ */ ++ deliver_payloads, ++ ++ /** ++ * Whether owners or publisher should receive replies to items ++ * ++ *

      Value: {@link ItemReply}

      ++ */ ++ itemreply, ++ ++ /** ++ * Who may associate leaf nodes with a collection ++ * ++ *

      Value: {@link ChildrenAssociationPolicy}

      ++ */ ++ children_association_policy, ++ ++ /** ++ * The list of JIDs that may associate leaf nodes with a ++ * collection ++ * ++ *

      Value: List of JIDs as Strings

      ++ */ ++ children_association_whitelist, ++ ++ /** ++ * The child nodes (leaf or collection) associated with a collection ++ * ++ *

      Value: List of Strings

      ++ */ ++ children, ++ ++ /** ++ * The maximum number of child nodes that can be associated with a ++ * collection ++ * ++ *

      Value: int

      ++ */ ++ children_max, ++ ++ /** ++ * The maximum number of items to persist ++ * ++ *

      Value: int

      ++ */ ++ max_items, ++ ++ /** ++ * The maximum payload size in bytes ++ * ++ *

      Value: int

      ++ */ ++ max_payload_size, ++ ++ /** ++ * Whether the node is a leaf (default) or collection ++ * ++ *

      Value: {@link NodeType}

      ++ */ ++ node_type, ++ ++ /** ++ * Whether to notify subscribers when the node configuration changes ++ * ++ *

      Value: boolean

      ++ */ ++ notify_config, ++ ++ /** ++ * Whether to notify subscribers when the node is deleted ++ * ++ *

      Value: boolean

      ++ */ ++ notify_delete, ++ ++ /** ++ * Whether to notify subscribers when items are removed from the node ++ * ++ *

      Value: boolean

      ++ */ ++ notify_retract, ++ ++ /** ++ * Whether to persist items to storage. This is required to have multiple ++ * items in the node. ++ * ++ *

      Value: boolean

      ++ */ ++ persist_items, ++ ++ /** ++ * Whether to deliver notifications to available users only ++ * ++ *

      Value: boolean

      ++ */ ++ presence_based_delivery, ++ ++ /** ++ * Defines who can publish to the node ++ * ++ *

      Value: {@link PublishModel}

      ++ */ ++ publish_model, ++ ++ /** ++ * The specific multi-user chat rooms to specify for replyroom ++ * ++ *

      Value: List of JIDs as Strings

      ++ */ ++ replyroom, ++ ++ /** ++ * The specific JID(s) to specify for replyto ++ * ++ *

      Value: List of JIDs as Strings

      ++ */ ++ replyto, ++ ++ /** ++ * The roster group(s) allowed to subscribe and retrieve items ++ * ++ *

      Value: List of strings

      ++ */ ++ roster_groups_allowed, ++ ++ /** ++ * Whether to allow subscriptions ++ * ++ *

      Value: boolean

      ++ */ ++ subscribe, ++ ++ /** ++ * A friendly name for the node ++ * ++ *

      Value: String

      ++ */ ++ title, ++ ++ /** ++ * The type of node data, ussually specified by the namespace ++ * of the payload(if any);MAY be a list-single rather than a ++ * text single ++ * ++ *

      Value: String

      ++ */ ++ type; ++ ++ public String getFieldName() ++ { ++ return "pubsub#" + toString(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/EmbeddedPacketExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/EmbeddedPacketExtension.java +new file mode 100644 +index 0000000..b17a66a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/EmbeddedPacketExtension.java +@@ -0,0 +1,45 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import java.util.List; ++ ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.util.PacketParserUtils; ++ ++/** ++ * This interface defines {@link PacketExtension} implementations that contain other ++ * extensions. This effectively extends the idea of an extension within one of the ++ * top level {@link Packet} types to consider any embedded element to be an extension ++ * of its parent. This more easily enables the usage of some of Smacks parsing ++ * utilities such as {@link PacketParserUtils#parsePacketExtension(String, String, org.xmlpull.v1.XmlPullParser)} to be used ++ * to parse any element of the XML being parsed. ++ * ++ *

      Top level extensions have only one element, but they can have multiple children, or ++ * their children can have multiple children. This interface is a way of allowing extensions ++ * to be embedded within one another as a partial or complete one to one mapping of extension ++ * to element. ++ * ++ * @author Robin Collier ++ */ ++public interface EmbeddedPacketExtension extends PacketExtension ++{ ++ /** ++ * Get the list of embedded {@link PacketExtension} objects. ++ * ++ * @return List of embedded {@link PacketExtension} ++ */ ++ List getExtensions(); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/EventElement.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/EventElement.java +new file mode 100644 +index 0000000..165970f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/EventElement.java +@@ -0,0 +1,74 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import java.util.Arrays; ++import java.util.List; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; ++ ++/** ++ * Represents the top level element of a pubsub event extension. All types of pubsub events are ++ * represented by this class. The specific type can be found by {@link #getEventType()}. The ++ * embedded event information, which is specific to the event type, can be retrieved by the {@link #getEvent()} ++ * method. ++ * ++ * @author Robin Collier ++ */ ++public class EventElement implements EmbeddedPacketExtension ++{ ++ private EventElementType type; ++ private NodeExtension ext; ++ ++ public EventElement(EventElementType eventType, NodeExtension eventExt) ++ { ++ type = eventType; ++ ext = eventExt; ++ } ++ ++ public EventElementType getEventType() ++ { ++ return type; ++ } ++ ++ public List getExtensions() ++ { ++ return Arrays.asList(new PacketExtension[]{getEvent()}); ++ } ++ ++ public NodeExtension getEvent() ++ { ++ return ext; ++ } ++ ++ public String getElementName() ++ { ++ return "event"; ++ } ++ ++ public String getNamespace() ++ { ++ return PubSubNamespace.EVENT.getXmlns(); ++ } ++ ++ public String toXML() ++ { ++ StringBuilder builder = new StringBuilder(""); ++ ++ builder.append(ext.toXML()); ++ builder.append(""); ++ return builder.toString(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/EventElementType.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/EventElementType.java +new file mode 100644 +index 0000000..343edbe +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/EventElementType.java +@@ -0,0 +1,41 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++/** ++ * This enumeration defines the possible event types that are supported within pubsub ++ * event messages. ++ * ++ * @author Robin Collier ++ */ ++public enum EventElementType ++{ ++ /** A node has been associated or dissassociated with a collection node */ ++ collection, ++ ++ /** A node has had its configuration changed */ ++ configuration, ++ ++ /** A node has been deleted */ ++ delete, ++ ++ /** Items have been published to a node */ ++ items, ++ ++ /** All items have been purged from a node */ ++ purge, ++ ++ /** A node has been subscribed to */ ++ subscription ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/FormNode.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/FormNode.java +new file mode 100644 +index 0000000..e08bed2 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/FormNode.java +@@ -0,0 +1,99 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import org.jivesoftware.smackx.Form; ++ ++/** ++ * Generic packet extension which represents any pubsub form that is ++ * parsed from the incoming stream or being sent out to the server. ++ * ++ * Form types are defined in {@link FormNodeType}. ++ * ++ * @author Robin Collier ++ */ ++public class FormNode extends NodeExtension ++{ ++ private Form configForm; ++ ++ /** ++ * Create a {@link FormNode} which contains the specified form. ++ * ++ * @param formType The type of form being sent ++ * @param submitForm The form ++ */ ++ public FormNode(FormNodeType formType, Form submitForm) ++ { ++ super(formType.getNodeElement()); ++ ++ if (submitForm == null) ++ throw new IllegalArgumentException("Submit form cannot be null"); ++ configForm = submitForm; ++ } ++ ++ /** ++ * Create a {@link FormNode} which contains the specified form, which is ++ * associated with the specified node. ++ * ++ * @param formType The type of form being sent ++ * @param nodeId The node the form is associated with ++ * @param submitForm The form ++ */ ++ public FormNode(FormNodeType formType, String nodeId, Form submitForm) ++ { ++ super(formType.getNodeElement(), nodeId); ++ ++ if (submitForm == null) ++ throw new IllegalArgumentException("Submit form cannot be null"); ++ configForm = submitForm; ++ } ++ ++ /** ++ * Get the Form that is to be sent, or was retrieved from the server. ++ * ++ * @return The form ++ */ ++ public Form getForm() ++ { ++ return configForm; ++ } ++ ++ @Override ++ public String toXML() ++ { ++ if (configForm == null) ++ { ++ return super.toXML(); ++ } ++ else ++ { ++ StringBuilder builder = new StringBuilder("<"); ++ builder.append(getElementName()); ++ ++ if (getNode() != null) ++ { ++ builder.append(" node='"); ++ builder.append(getNode()); ++ builder.append("'>"); ++ } ++ else ++ builder.append('>'); ++ builder.append(configForm.getDataFormToSend().toXML()); ++ builder.append("'); ++ return builder.toString(); ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/FormNodeType.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/FormNodeType.java +new file mode 100644 +index 0000000..6a163ee +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/FormNodeType.java +@@ -0,0 +1,50 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; ++ ++/** ++ * The types of forms supported by the pubsub specification. ++ * ++ * @author Robin Collier ++ */ ++public enum FormNodeType ++{ ++ /** Form for configuring an existing node */ ++ CONFIGURE_OWNER, ++ ++ /** Form for configuring a node during creation */ ++ CONFIGURE, ++ ++ /** Form for configuring subscription options */ ++ OPTIONS, ++ ++ /** Form which represents the default node configuration options */ ++ DEFAULT; ++ ++ public PubSubElementType getNodeElement() ++ { ++ return PubSubElementType.valueOf(toString()); ++ } ++ ++ public static FormNodeType valueOfFromElementName(String elem, String configNamespace) ++ { ++ if ("configure".equals(elem) && PubSubNamespace.OWNER.getXmlns().equals(configNamespace)) ++ { ++ return CONFIGURE_OWNER; ++ } ++ return valueOf(elem.toUpperCase()); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/FormType.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/FormType.java +new file mode 100644 +index 0000000..e0fff51 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/FormType.java +@@ -0,0 +1,26 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import org.jivesoftware.smackx.Form; ++ ++/** ++ * Defines the allowable types for a {@link Form} ++ * ++ * @author Robin Collier ++ */ ++public enum FormType ++{ ++ form, submit, cancel, result; ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/GetItemsRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/GetItemsRequest.java +new file mode 100644 +index 0000000..341b7b5 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/GetItemsRequest.java +@@ -0,0 +1,85 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++/** ++ * Represents a request to subscribe to a node. ++ * ++ * @author Robin Collier ++ */ ++public class GetItemsRequest extends NodeExtension ++{ ++ protected String subId; ++ protected int maxItems; ++ ++ public GetItemsRequest(String nodeId) ++ { ++ super(PubSubElementType.ITEMS, nodeId); ++ } ++ ++ public GetItemsRequest(String nodeId, String subscriptionId) ++ { ++ super(PubSubElementType.ITEMS, nodeId); ++ subId = subscriptionId; ++ } ++ ++ public GetItemsRequest(String nodeId, int maxItemsToReturn) ++ { ++ super(PubSubElementType.ITEMS, nodeId); ++ maxItems = maxItemsToReturn; ++ } ++ ++ public GetItemsRequest(String nodeId, String subscriptionId, int maxItemsToReturn) ++ { ++ this(nodeId, maxItemsToReturn); ++ subId = subscriptionId; ++ } ++ ++ public String getSubscriptionId() ++ { ++ return subId; ++ } ++ ++ public int getMaxItems() ++ { ++ return maxItems; ++ } ++ ++ @Override ++ public String toXML() ++ { ++ StringBuilder builder = new StringBuilder("<"); ++ builder.append(getElementName()); ++ ++ builder.append(" node='"); ++ builder.append(getNode()); ++ builder.append("'"); ++ ++ if (getSubscriptionId() != null) ++ { ++ builder.append(" subid='"); ++ builder.append(getSubscriptionId()); ++ builder.append("'"); ++ } ++ ++ if (getMaxItems() > 0) ++ { ++ builder.append(" max_items='"); ++ builder.append(getMaxItems()); ++ builder.append("'"); ++ } ++ builder.append("/>"); ++ return builder.toString(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Item.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Item.java +new file mode 100644 +index 0000000..2ce0baa +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Item.java +@@ -0,0 +1,132 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import org.jivesoftware.smack.packet.Message; ++import org.jivesoftware.smackx.pubsub.provider.ItemProvider; ++ ++/** ++ * This class represents an item that has been, or will be published to a ++ * pubsub node. An Item has several properties that are dependent ++ * on the configuration of the node to which it has been or will be published. ++ * ++ *

      An Item received from a node (via {@link LeafNode#getItems()} or {@link LeafNode#addItemEventListener(org.jivesoftware.smackx.pubsub.listener.ItemEventListener)} ++ *
    • Will always have an id (either user or server generated) unless node configuration has both ++ * {@link ConfigureForm#isPersistItems()} and {@link ConfigureForm#isDeliverPayloads()}set to false. ++ *
    • Will have a payload if the node configuration has {@link ConfigureForm#isDeliverPayloads()} set ++ * to true, otherwise it will be null. ++ * ++ *

      An Item created to send to a node (via {@link LeafNode#send()} or {@link LeafNode#publish()} ++ *
    • The id is optional, since the server will generate one if necessary, but should be used if it is ++ * meaningful in the context of the node. This value must be unique within the node that it is sent to, since ++ * resending an item with the same id will overwrite the one that already exists if the items are persisted. ++ *
    • Will require payload if the node configuration has {@link ConfigureForm#isDeliverPayloads()} set ++ * to true. ++ * ++ *

      To customise the payload object being returned from the {@link #getPayload()} method, you can ++ * add a custom parser as explained in {@link ItemProvider}. ++ * ++ * @author Robin Collier ++ */ ++public class Item extends NodeExtension ++{ ++ private String id; ++ ++ /** ++ * Create an empty Item with no id. This is a valid item for nodes which are configured ++ * so that {@link ConfigureForm#isDeliverPayloads()} is false. In most cases an id will be generated by the server. ++ * For nodes configured with {@link ConfigureForm#isDeliverPayloads()} and {@link ConfigureForm#isPersistItems()} ++ * set to false, no Item is sent to the node, you have to use {@link LeafNode#send()} or {@link LeafNode#publish()} ++ * methods in this case. ++ */ ++ public Item() ++ { ++ super(PubSubElementType.ITEM); ++ } ++ ++ /** ++ * Create an Item with an id but no payload. This is a valid item for nodes which are configured ++ * so that {@link ConfigureForm#isDeliverPayloads()} is false. ++ * ++ * @param itemId The id if the item. It must be unique within the node unless overwriting and existing item. ++ * Passing null is the equivalent of calling {@link #Item()}. ++ */ ++ public Item(String itemId) ++ { ++ // The element type is actually irrelevant since we override getNamespace() to return null ++ super(PubSubElementType.ITEM); ++ id = itemId; ++ } ++ ++ /** ++ * Create an Item with an id and a node id. ++ *

      ++ * Note: This is not valid for publishing an item to a node, only receiving from ++ * one as part of {@link Message}. If used to create an Item to publish ++ * (via {@link LeafNode#publish(Item)}, the server may return an ++ * error for an invalid packet. ++ * ++ * @param itemId The id of the item. ++ * @param nodeId The id of the node which the item was published to. ++ */ ++ public Item(String itemId, String nodeId) ++ { ++ super(PubSubElementType.ITEM_EVENT, nodeId); ++ id = itemId; ++ } ++ ++ /** ++ * Get the item id. Unique to the node it is associated with. ++ * ++ * @return The id ++ */ ++ public String getId() ++ { ++ return id; ++ } ++ ++ @Override ++ public String getNamespace() ++ { ++ return null; ++ } ++ ++ @Override ++ public String toXML() ++ { ++ StringBuilder builder = new StringBuilder(""); ++ ++ return builder.toString(); ++ } ++ ++ @Override ++ public String toString() ++ { ++ return getClass().getName() + " | Content [" + toXML() + "]"; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemDeleteEvent.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemDeleteEvent.java +new file mode 100644 +index 0000000..82ab7df +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemDeleteEvent.java +@@ -0,0 +1,62 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import java.util.Collections; ++import java.util.List; ++ ++/** ++ * Represents an event in which items have been deleted from the node. ++ * ++ * @author Robin Collier ++ */ ++public class ItemDeleteEvent extends SubscriptionEvent ++{ ++ private List itemIds = Collections.EMPTY_LIST; ++ ++ /** ++ * Constructs an ItemDeleteEvent that indicates the the supplied ++ * items (by id) have been deleted, and that the event matches the listed ++ * subscriptions. The subscriptions would have been created by calling ++ * {@link LeafNode#subscribe(String)}. ++ * ++ * @param nodeId The id of the node the event came from ++ * @param deletedItemIds The item ids of the items that were deleted. ++ * @param subscriptionIds The subscriptions that match the event. ++ */ ++ public ItemDeleteEvent(String nodeId, List deletedItemIds, List subscriptionIds) ++ { ++ super(nodeId, subscriptionIds); ++ ++ if (deletedItemIds == null) ++ throw new IllegalArgumentException("deletedItemIds cannot be null"); ++ itemIds = deletedItemIds; ++ } ++ ++ /** ++ * Get the item id's of the items that have been deleted. ++ * ++ * @return List of item id's ++ */ ++ public List getItemIds() ++ { ++ return Collections.unmodifiableList(itemIds); ++ } ++ ++ @Override ++ public String toString() ++ { ++ return getClass().getName() + " [subscriptions: " + getSubscriptions() + "], [Deleted Items: " + itemIds + ']'; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemPublishEvent.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemPublishEvent.java +new file mode 100644 +index 0000000..1ef1f67 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemPublishEvent.java +@@ -0,0 +1,123 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import java.util.Collections; ++import java.util.Date; ++import java.util.List; ++ ++/** ++ * Represents an event generated by an item(s) being published to a node. ++ * ++ * @author Robin Collier ++ */ ++public class ItemPublishEvent extends SubscriptionEvent ++{ ++ private List items; ++ private Date originalDate; ++ ++ /** ++ * Constructs an ItemPublishEvent with the provided list ++ * of {@link Item} that were published. ++ * ++ * @param nodeId The id of the node the event came from ++ * @param eventItems The list of {@link Item} that were published ++ */ ++ public ItemPublishEvent(String nodeId, List eventItems) ++ { ++ super(nodeId); ++ items = eventItems; ++ } ++ ++ /** ++ * Constructs an ItemPublishEvent with the provided list ++ * of {@link Item} that were published. The list of subscription ids ++ * represents the subscriptions that matched the event, in the case ++ * of the user having multiple subscriptions. ++ * ++ * @param nodeId The id of the node the event came from ++ * @param eventItems The list of {@link Item} that were published ++ * @param subscriptionIds The list of subscriptionIds ++ */ ++ public ItemPublishEvent(String nodeId, List eventItems, List subscriptionIds) ++ { ++ super(nodeId, subscriptionIds); ++ items = eventItems; ++ } ++ ++ /** ++ * Constructs an ItemPublishEvent with the provided list ++ * of {@link Item} that were published in the past. The published ++ * date signifies that this is delayed event. The list of subscription ids ++ * represents the subscriptions that matched the event, in the case ++ * of the user having multiple subscriptions. ++ * ++ * @param nodeId The id of the node the event came from ++ * @param eventItems The list of {@link Item} that were published ++ * @param subscriptionIds The list of subscriptionIds ++ * @param publishedDate The original publishing date of the events ++ */ ++ public ItemPublishEvent(String nodeId, List eventItems, List subscriptionIds, Date publishedDate) ++ { ++ super(nodeId, subscriptionIds); ++ items = eventItems; ++ ++ if (publishedDate != null) ++ originalDate = publishedDate; ++ } ++ ++ /** ++ * Get the list of {@link Item} that were published. ++ * ++ * @return The list of published {@link Item} ++ */ ++ public List getItems() ++ { ++ return Collections.unmodifiableList(items); ++ } ++ ++ /** ++ * Indicates whether this event was delayed. That is, the items ++ * were published to the node at some time in the past. This will ++ * typically happen if there is an item already published to the node ++ * before a user subscribes to it. In this case, when the user ++ * subscribes, the server may send the last item published to the node ++ * with a delay date showing its time of original publication. ++ * ++ * @return true if the items are delayed, false otherwise. ++ */ ++ public boolean isDelayed() ++ { ++ return (originalDate != null); ++ } ++ ++ /** ++ * Gets the original date the items were published. This is only ++ * valid if {@link #isDelayed()} is true. ++ * ++ * @return Date items were published if {@link #isDelayed()} is true, null otherwise. ++ */ ++ public Date getPublishedDate() ++ { ++ return originalDate; ++ } ++ ++ @Override ++ public String toString() ++ { ++ return getClass().getName() + " [subscriptions: " + getSubscriptions() + "], [Delayed: " + ++ (isDelayed() ? originalDate.toString() : "false") + ']'; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemReply.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemReply.java +new file mode 100644 +index 0000000..3e090d9 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemReply.java +@@ -0,0 +1,29 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++/** ++ * These are the options for the node configuration setting {@link ConfigureForm#setItemReply(ItemReply)}, ++ * which defines who should receive replies to items. ++ * ++ * @author Robin Collier ++ */ ++public enum ItemReply ++{ ++ /** The node owner */ ++ owner, ++ ++ /** The item publisher */ ++ publisher; ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemsExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemsExtension.java +new file mode 100644 +index 0000000..c98d93a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemsExtension.java +@@ -0,0 +1,196 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import java.util.List; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++/** ++ * This class is used to for multiple purposes. ++ *

    • It can represent an event containing a list of items that have been published ++ *
    • It can represent an event containing a list of retracted (deleted) items. ++ *
    • It can represent a request to delete a list of items. ++ *
    • It can represent a request to get existing items. ++ * ++ *

      Please note, this class is used for internal purposes, and is not required for usage of ++ * pubsub functionality. ++ * ++ * @author Robin Collier ++ */ ++public class ItemsExtension extends NodeExtension implements EmbeddedPacketExtension ++{ ++ protected ItemsElementType type; ++ protected Boolean notify; ++ protected List items; ++ ++ public enum ItemsElementType ++ { ++ /** An items element, which has an optional max_items attribute when requesting items */ ++ items(PubSubElementType.ITEMS, "max_items"), ++ ++ /** A retract element, which has an optional notify attribute when publishing deletions */ ++ retract(PubSubElementType.RETRACT, "notify"); ++ ++ private PubSubElementType elem; ++ private String att; ++ ++ private ItemsElementType(PubSubElementType nodeElement, String attribute) ++ { ++ elem = nodeElement; ++ att = attribute; ++ } ++ ++ public PubSubElementType getNodeElement() ++ { ++ return elem; ++ } ++ ++ public String getElementAttribute() ++ { ++ return att; ++ } ++ } ++ ++ /** ++ * Construct an instance with a list representing items that have been published or deleted. ++ * ++ *

      Valid scenarios are: ++ *

    • Request items from node - itemsType = {@link ItemsElementType#items}, items = list of {@link Item} and an ++ * optional value for the max_items attribute. ++ *
    • Request to delete items - itemsType = {@link ItemsElementType#retract}, items = list of {@link Item} containing ++ * only id's and an optional value for the notify attribute. ++ *
    • Items published event - itemsType = {@link ItemsElementType#items}, items = list of {@link Item} and ++ * attributeValue = null ++ *
    • Items deleted event - itemsType = {@link ItemsElementType#items}, items = list of {@link RetractItem} and ++ * attributeValue = null ++ * ++ * @param itemsType Type of representation ++ * @param nodeId The node to which the items are being sent or deleted ++ * @param items The list of {@link Item} or {@link RetractItem} ++ * @param attributeValue The value of the max_items ++ */ ++ public ItemsExtension(ItemsElementType itemsType, String nodeId, List items) ++ { ++ super(itemsType.getNodeElement(), nodeId); ++ type = itemsType; ++ this.items = items; ++ } ++ ++ /** ++ * Construct an instance with a list representing items that have been published or deleted. ++ * ++ *

      Valid scenarios are: ++ *

    • Request items from node - itemsType = {@link ItemsElementType#items}, items = list of {@link Item} and an ++ * optional value for the max_items attribute. ++ *
    • Request to delete items - itemsType = {@link ItemsElementType#retract}, items = list of {@link Item} containing ++ * only id's and an optional value for the notify attribute. ++ *
    • Items published event - itemsType = {@link ItemsElementType#items}, items = list of {@link Item} and ++ * attributeValue = null ++ *
    • Items deleted event - itemsType = {@link ItemsElementType#items}, items = list of {@link RetractItem} and ++ * attributeValue = null ++ * ++ * @param itemsType Type of representation ++ * @param nodeId The node to which the items are being sent or deleted ++ * @param items The list of {@link Item} or {@link RetractItem} ++ * @param attributeValue The value of the max_items ++ */ ++ public ItemsExtension(String nodeId, List items, boolean notify) ++ { ++ super(ItemsElementType.retract.getNodeElement(), nodeId); ++ type = ItemsElementType.retract; ++ this.items = items; ++ this.notify = notify; ++ } ++ ++ /** ++ * Get the type of element ++ * ++ * @return The element type ++ */ ++ public ItemsElementType getItemsElementType() ++ { ++ return type; ++ } ++ ++ public List getExtensions() ++ { ++ return (List)getItems(); ++ } ++ ++ /** ++ * Gets the items related to the type of request or event. ++ * ++ * return List of {@link Item}, {@link RetractItem}, or null ++ */ ++ public List getItems() ++ { ++ return items; ++ } ++ ++ /** ++ * Gets the value of the optional attribute related to the {@link ItemsElementType}. ++ * ++ * @return The attribute value ++ */ ++ public boolean getNotify() ++ { ++ return notify; ++ } ++ ++ @Override ++ public String toXML() ++ { ++ if ((items == null) || (items.size() == 0)) ++ { ++ return super.toXML(); ++ } ++ else ++ { ++ StringBuilder builder = new StringBuilder("<"); ++ builder.append(getElementName()); ++ builder.append(" node='"); ++ builder.append(getNode()); ++ ++ if (notify != null) ++ { ++ builder.append("' "); ++ builder.append(type.getElementAttribute()); ++ builder.append("='"); ++ builder.append(notify.equals(Boolean.TRUE) ? 1 : 0); ++ builder.append("'>"); ++ } ++ else ++ { ++ builder.append("'>"); ++ for (PacketExtension item : items) ++ { ++ builder.append(item.toXML()); ++ } ++ } ++ ++ builder.append(""); ++ return builder.toString(); ++ } ++ } ++ ++ @Override ++ public String toString() ++ { ++ return getClass().getName() + "Content [" + toXML() + "]"; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/LeafNode.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/LeafNode.java +new file mode 100644 +index 0000000..eee6293 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/LeafNode.java +@@ -0,0 +1,352 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.List; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.packet.IQ.Type; ++import org.jivesoftware.smackx.packet.DiscoverItems; ++import org.jivesoftware.smackx.pubsub.packet.PubSub; ++import org.jivesoftware.smackx.pubsub.packet.SyncPacketSend; ++ ++/** ++ * The main class for the majority of pubsub functionality. In general ++ * almost all pubsub capabilities are related to the concept of a node. ++ * All items are published to a node, and typically subscribed to by other ++ * users. These users then retrieve events based on this subscription. ++ * ++ * @author Robin Collier ++ */ ++public class LeafNode extends Node ++{ ++ LeafNode(Connection connection, String nodeName) ++ { ++ super(connection, nodeName); ++ } ++ ++ /** ++ * Get information on the items in the node in standard ++ * {@link DiscoverItems} format. ++ * ++ * @return The item details in {@link DiscoverItems} format ++ * ++ * @throws XMPPException ++ */ ++ public DiscoverItems discoverItems() ++ throws XMPPException ++ { ++ DiscoverItems items = new DiscoverItems(); ++ items.setTo(to); ++ items.setNode(getId()); ++ return (DiscoverItems)SyncPacketSend.getReply(con, items); ++ } ++ ++ /** ++ * Get the current items stored in the node. ++ * ++ * @return List of {@link Item} in the node ++ * ++ * @throws XMPPException ++ */ ++ public List getItems() ++ throws XMPPException ++ { ++ PubSub request = createPubsubPacket(Type.GET, new GetItemsRequest(getId())); ++ ++ PubSub result = (PubSub)SyncPacketSend.getReply(con, request); ++ ItemsExtension itemsElem = (ItemsExtension)result.getExtension(PubSubElementType.ITEMS); ++ return (List)itemsElem.getItems(); ++ } ++ ++ /** ++ * Get the current items stored in the node based ++ * on the subscription associated with the provided ++ * subscription id. ++ * ++ * @param subscriptionId - The subscription id for the ++ * associated subscription. ++ * @return List of {@link Item} in the node ++ * ++ * @throws XMPPException ++ */ ++ public List getItems(String subscriptionId) ++ throws XMPPException ++ { ++ PubSub request = createPubsubPacket(Type.GET, new GetItemsRequest(getId(), subscriptionId)); ++ ++ PubSub result = (PubSub)SyncPacketSend.getReply(con, request); ++ ItemsExtension itemsElem = (ItemsExtension)result.getExtension(PubSubElementType.ITEMS); ++ return (List)itemsElem.getItems(); ++ } ++ ++ /** ++ * Get the items specified from the node. This would typically be ++ * used when the server does not return the payload due to size ++ * constraints. The user would be required to retrieve the payload ++ * after the items have been retrieved via {@link #getItems()} or an ++ * event, that did not include the payload. ++ * ++ * @param ids Item ids of the items to retrieve ++ * ++ * @return The list of {@link Item} with payload ++ * ++ * @throws XMPPException ++ */ ++ public List getItems(Collection ids) ++ throws XMPPException ++ { ++ List itemList = new ArrayList(ids.size()); ++ ++ for (String id : ids) ++ { ++ itemList.add(new Item(id)); ++ } ++ PubSub request = createPubsubPacket(Type.GET, new ItemsExtension(ItemsExtension.ItemsElementType.items, getId(), itemList)); ++ ++ PubSub result = (PubSub)SyncPacketSend.getReply(con, request); ++ ItemsExtension itemsElem = (ItemsExtension)result.getExtension(PubSubElementType.ITEMS); ++ return (List)itemsElem.getItems(); ++ } ++ ++ /** ++ * Get items persisted on the node, limited to the specified number. ++ * ++ * @param maxItems Maximum number of items to return ++ * ++ * @return List of {@link Item} ++ * ++ * @throws XMPPException ++ */ ++ public List getItems(int maxItems) ++ throws XMPPException ++ { ++ PubSub request = createPubsubPacket(Type.GET, new GetItemsRequest(getId(), maxItems)); ++ ++ PubSub result = (PubSub)SyncPacketSend.getReply(con, request); ++ ItemsExtension itemsElem = (ItemsExtension)result.getExtension(PubSubElementType.ITEMS); ++ return (List)itemsElem.getItems(); ++ } ++ ++ /** ++ * Get items persisted on the node, limited to the specified number ++ * based on the subscription associated with the provided subscriptionId. ++ * ++ * @param maxItems Maximum number of items to return ++ * @param subscriptionId The subscription which the retrieval is based ++ * on. ++ * ++ * @return List of {@link Item} ++ * ++ * @throws XMPPException ++ */ ++ public List getItems(int maxItems, String subscriptionId) ++ throws XMPPException ++ { ++ PubSub request = createPubsubPacket(Type.GET, new GetItemsRequest(getId(), subscriptionId, maxItems)); ++ ++ PubSub result = (PubSub)SyncPacketSend.getReply(con, request); ++ ItemsExtension itemsElem = (ItemsExtension)result.getExtension(PubSubElementType.ITEMS); ++ return (List)itemsElem.getItems(); ++ } ++ ++ /** ++ * Publishes an event to the node. This is an empty event ++ * with no item. ++ * ++ * This is only acceptable for nodes with {@link ConfigureForm#isPersistItems()}=false ++ * and {@link ConfigureForm#isDeliverPayloads()}=false. ++ * ++ * This is an asynchronous call which returns as soon as the ++ * packet has been sent. ++ * ++ * For synchronous calls use {@link #send() send()}. ++ */ ++ public void publish() ++ { ++ PubSub packet = createPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.PUBLISH, getId())); ++ ++ con.sendPacket(packet); ++ } ++ ++ /** ++ * Publishes an event to the node. This is a simple item ++ * with no payload. ++ * ++ * If the id is null, an empty item (one without an id) will be sent. ++ * Please note that this is not the same as {@link #send()}, which ++ * publishes an event with NO item. ++ * ++ * This is an asynchronous call which returns as soon as the ++ * packet has been sent. ++ * ++ * For synchronous calls use {@link #send(Item) send(Item))}. ++ * ++ * @param item - The item being sent ++ */ ++ public void publish(T item) ++ { ++ Collection items = new ArrayList(1); ++ items.add((T)(item == null ? new Item() : item)); ++ publish(items); ++ } ++ ++ /** ++ * Publishes multiple events to the node. Same rules apply as in {@link #publish(Item)}. ++ * ++ * In addition, if {@link ConfigureForm#isPersistItems()}=false, only the last item in the input ++ * list will get stored on the node, assuming it stores the last sent item. ++ * ++ * This is an asynchronous call which returns as soon as the ++ * packet has been sent. ++ * ++ * For synchronous calls use {@link #send(Collection) send(Collection))}. ++ * ++ * @param items - The collection of items being sent ++ */ ++ public void publish(Collection items) ++ { ++ PubSub packet = createPubsubPacket(Type.SET, new PublishItem(getId(), items)); ++ ++ con.sendPacket(packet); ++ } ++ ++ /** ++ * Publishes an event to the node. This is an empty event ++ * with no item. ++ * ++ * This is only acceptable for nodes with {@link ConfigureForm#isPersistItems()}=false ++ * and {@link ConfigureForm#isDeliverPayloads()}=false. ++ * ++ * This is a synchronous call which will throw an exception ++ * on failure. ++ * ++ * For asynchronous calls, use {@link #publish() publish()}. ++ * ++ * @throws XMPPException ++ */ ++ public void send() ++ throws XMPPException ++ { ++ PubSub packet = createPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.PUBLISH, getId())); ++ ++ SyncPacketSend.getReply(con, packet); ++ } ++ ++ /** ++ * Publishes an event to the node. This can be either a simple item ++ * with no payload, or one with it. This is determined by the Node ++ * configuration. ++ * ++ * If the node has deliver_payload=false, the Item must not ++ * have a payload. ++ * ++ * If the id is null, an empty item (one without an id) will be sent. ++ * Please note that this is not the same as {@link #send()}, which ++ * publishes an event with NO item. ++ * ++ * This is a synchronous call which will throw an exception ++ * on failure. ++ * ++ * For asynchronous calls, use {@link #publish(Item) publish(Item)}. ++ * ++ * @param item - The item being sent ++ * ++ * @throws XMPPException ++ */ ++ public void send(T item) ++ throws XMPPException ++ { ++ Collection items = new ArrayList(1); ++ items.add((item == null ? (T)new Item() : item)); ++ send(items); ++ } ++ ++ /** ++ * Publishes multiple events to the node. Same rules apply as in {@link #send(Item)}. ++ * ++ * In addition, if {@link ConfigureForm#isPersistItems()}=false, only the last item in the input ++ * list will get stored on the node, assuming it stores the last sent item. ++ * ++ * This is a synchronous call which will throw an exception ++ * on failure. ++ * ++ * For asynchronous calls, use {@link #publish(Collection) publish(Collection))}. ++ * ++ * @param items - The collection of {@link Item} objects being sent ++ * ++ * @throws XMPPException ++ */ ++ public void send(Collection items) ++ throws XMPPException ++ { ++ PubSub packet = createPubsubPacket(Type.SET, new PublishItem(getId(), items)); ++ ++ SyncPacketSend.getReply(con, packet); ++ } ++ ++ /** ++ * Purges the node of all items. ++ * ++ *

      Note: Some implementations may keep the last item ++ * sent. ++ * ++ * @throws XMPPException ++ */ ++ public void deleteAllItems() ++ throws XMPPException ++ { ++ PubSub request = createPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.PURGE_OWNER, getId()), PubSubElementType.PURGE_OWNER.getNamespace()); ++ ++ SyncPacketSend.getReply(con, request); ++ } ++ ++ /** ++ * Delete the item with the specified id from the node. ++ * ++ * @param itemId The id of the item ++ * ++ * @throws XMPPException ++ */ ++ public void deleteItem(String itemId) ++ throws XMPPException ++ { ++ Collection items = new ArrayList(1); ++ items.add(itemId); ++ deleteItem(items); ++ } ++ ++ /** ++ * Delete the items with the specified id's from the node. ++ * ++ * @param itemIds The list of id's of items to delete ++ * ++ * @throws XMPPException ++ */ ++ public void deleteItem(Collection itemIds) ++ throws XMPPException ++ { ++ List items = new ArrayList(itemIds.size()); ++ ++ for (String id : itemIds) ++ { ++ items.add(new Item(id)); ++ } ++ PubSub request = createPubsubPacket(Type.SET, new ItemsExtension(ItemsExtension.ItemsElementType.retract, getId(), items)); ++ SyncPacketSend.getReply(con, request); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Node.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Node.java +new file mode 100644 +index 0000000..c900493 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Node.java +@@ -0,0 +1,525 @@ ++/* ++ * Created on 2009-07-09 ++ */ ++package org.jivesoftware.smackx.pubsub; ++ ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.Iterator; ++import java.util.List; ++import java.util.concurrent.ConcurrentHashMap; ++ ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.OrFilter; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.packet.Message; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.packet.IQ.Type; ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.packet.DelayInformation; ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++import org.jivesoftware.smackx.packet.Header; ++import org.jivesoftware.smackx.packet.HeadersExtension; ++import org.jivesoftware.smackx.pubsub.listener.ItemDeleteListener; ++import org.jivesoftware.smackx.pubsub.listener.ItemEventListener; ++import org.jivesoftware.smackx.pubsub.listener.NodeConfigListener; ++import org.jivesoftware.smackx.pubsub.packet.PubSub; ++import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; ++import org.jivesoftware.smackx.pubsub.packet.SyncPacketSend; ++import org.jivesoftware.smackx.pubsub.util.NodeUtils; ++ ++abstract public class Node ++{ ++ protected Connection con; ++ protected String id; ++ protected String to; ++ ++ protected ConcurrentHashMap itemEventToListenerMap = new ConcurrentHashMap(); ++ protected ConcurrentHashMap itemDeleteToListenerMap = new ConcurrentHashMap(); ++ protected ConcurrentHashMap configEventToListenerMap = new ConcurrentHashMap(); ++ ++ /** ++ * Construct a node associated to the supplied connection with the specified ++ * node id. ++ * ++ * @param connection The connection the node is associated with ++ * @param nodeName The node id ++ */ ++ Node(Connection connection, String nodeName) ++ { ++ con = connection; ++ id = nodeName; ++ } ++ ++ /** ++ * Some XMPP servers may require a specific service to be addressed on the ++ * server. ++ * ++ * For example, OpenFire requires the server to be prefixed by pubsub ++ */ ++ void setTo(String toAddress) ++ { ++ to = toAddress; ++ } ++ ++ /** ++ * Get the NodeId ++ * ++ * @return the node id ++ */ ++ public String getId() ++ { ++ return id; ++ } ++ /** ++ * Returns a configuration form, from which you can create an answer form to be submitted ++ * via the {@link #sendConfigurationForm(Form)}. ++ * ++ * @return the configuration form ++ */ ++ public ConfigureForm getNodeConfiguration() ++ throws XMPPException ++ { ++ Packet reply = sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.CONFIGURE_OWNER, getId()), PubSubNamespace.OWNER); ++ return NodeUtils.getFormFromPacket(reply, PubSubElementType.CONFIGURE_OWNER); ++ } ++ ++ /** ++ * Update the configuration with the contents of the new {@link Form} ++ * ++ * @param submitForm ++ */ ++ public void sendConfigurationForm(Form submitForm) ++ throws XMPPException ++ { ++ PubSub packet = createPubsubPacket(Type.SET, new FormNode(FormNodeType.CONFIGURE_OWNER, getId(), submitForm), PubSubNamespace.OWNER); ++ SyncPacketSend.getReply(con, packet); ++ } ++ ++ /** ++ * Discover node information in standard {@link DiscoverInfo} format. ++ * ++ * @return The discovery information about the node. ++ * ++ * @throws XMPPException ++ */ ++ public DiscoverInfo discoverInfo() ++ throws XMPPException ++ { ++ DiscoverInfo info = new DiscoverInfo(); ++ info.setTo(to); ++ info.setNode(getId()); ++ return (DiscoverInfo)SyncPacketSend.getReply(con, info); ++ } ++ ++ /** ++ * Get the subscriptions currently associated with this node. ++ * ++ * @return List of {@link Subscription} ++ * ++ * @throws XMPPException ++ */ ++ public List getSubscriptions() ++ throws XMPPException ++ { ++ PubSub reply = (PubSub)sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.SUBSCRIPTIONS, getId())); ++ SubscriptionsExtension subElem = (SubscriptionsExtension)reply.getExtension(PubSubElementType.SUBSCRIPTIONS); ++ return subElem.getSubscriptions(); ++ } ++ ++ /** ++ * The user subscribes to the node using the supplied jid. The ++ * bare jid portion of this one must match the jid for the connection. ++ * ++ * Please note that the {@link Subscription.State} should be checked ++ * on return since more actions may be required by the caller. ++ * {@link Subscription.State#pending} - The owner must approve the subscription ++ * request before messages will be received. ++ * {@link Subscription.State#unconfigured} - If the {@link Subscription#isConfigRequired()} is true, ++ * the caller must configure the subscription before messages will be received. If it is false ++ * the caller can configure it but is not required to do so. ++ * @param jid The jid to subscribe as. ++ * @return The subscription ++ * @exception XMPPException ++ */ ++ public Subscription subscribe(String jid) ++ throws XMPPException ++ { ++ PubSub reply = (PubSub)sendPubsubPacket(Type.SET, new SubscribeExtension(jid, getId())); ++ return (Subscription)reply.getExtension(PubSubElementType.SUBSCRIPTION); ++ } ++ ++ /** ++ * The user subscribes to the node using the supplied jid and subscription ++ * options. The bare jid portion of this one must match the jid for the ++ * connection. ++ * ++ * Please note that the {@link Subscription.State} should be checked ++ * on return since more actions may be required by the caller. ++ * {@link Subscription.State#pending} - The owner must approve the subscription ++ * request before messages will be received. ++ * {@link Subscription.State#unconfigured} - If the {@link Subscription#isConfigRequired()} is true, ++ * the caller must configure the subscription before messages will be received. If it is false ++ * the caller can configure it but is not required to do so. ++ * @param jid The jid to subscribe as. ++ * @return The subscription ++ * @exception XMPPException ++ */ ++ public Subscription subscribe(String jid, SubscribeForm subForm) ++ throws XMPPException ++ { ++ PubSub request = createPubsubPacket(Type.SET, new SubscribeExtension(jid, getId())); ++ request.addExtension(new FormNode(FormNodeType.OPTIONS, subForm)); ++ PubSub reply = (PubSub)PubSubManager.sendPubsubPacket(con, jid, Type.SET, request); ++ return (Subscription)reply.getExtension(PubSubElementType.SUBSCRIPTION); ++ } ++ ++ /** ++ * Remove the subscription related to the specified JID. This will only ++ * work if there is only 1 subscription. If there are multiple subscriptions, ++ * use {@link #unsubscribe(String, String)}. ++ * ++ * @param jid The JID used to subscribe to the node ++ * ++ * @throws XMPPException ++ */ ++ public void unsubscribe(String jid) ++ throws XMPPException ++ { ++ unsubscribe(jid, null); ++ } ++ ++ /** ++ * Remove the specific subscription related to the specified JID. ++ * ++ * @param jid The JID used to subscribe to the node ++ * @param subscriptionId The id of the subscription being removed ++ * ++ * @throws XMPPException ++ */ ++ public void unsubscribe(String jid, String subscriptionId) ++ throws XMPPException ++ { ++ sendPubsubPacket(Type.SET, new UnsubscribeExtension(jid, getId(), subscriptionId)); ++ } ++ ++ /** ++ * Returns a SubscribeForm for subscriptions, from which you can create an answer form to be submitted ++ * via the {@link #sendConfigurationForm(Form)}. ++ * ++ * @return A subscription options form ++ * ++ * @throws XMPPException ++ */ ++ public SubscribeForm getSubscriptionOptions(String jid) ++ throws XMPPException ++ { ++ return getSubscriptionOptions(jid, null); ++ } ++ ++ ++ /** ++ * Get the options for configuring the specified subscription. ++ * ++ * @param jid JID the subscription is registered under ++ * @param subscriptionId The subscription id ++ * ++ * @return The subscription option form ++ * ++ * @throws XMPPException ++ */ ++ public SubscribeForm getSubscriptionOptions(String jid, String subscriptionId) ++ throws XMPPException ++ { ++ PubSub packet = (PubSub)sendPubsubPacket(Type.GET, new OptionsExtension(jid, getId(), subscriptionId)); ++ FormNode ext = (FormNode)packet.getExtension(PubSubElementType.OPTIONS); ++ return new SubscribeForm(ext.getForm()); ++ } ++ ++ /** ++ * Register a listener for item publication events. This ++ * listener will get called whenever an item is published to ++ * this node. ++ * ++ * @param listener The handler for the event ++ */ ++ public void addItemEventListener(ItemEventListener listener) ++ { ++ PacketListener conListener = new ItemEventTranslator(listener); ++ itemEventToListenerMap.put(listener, conListener); ++ con.addPacketListener(conListener, new EventContentFilter(EventElementType.items.toString(), "item")); ++ } ++ ++ /** ++ * Unregister a listener for publication events. ++ * ++ * @param listener The handler to unregister ++ */ ++ public void removeItemEventListener(ItemEventListener listener) ++ { ++ PacketListener conListener = itemEventToListenerMap.remove(listener); ++ ++ if (conListener != null) ++ con.removePacketListener(conListener); ++ } ++ ++ /** ++ * Register a listener for configuration events. This listener ++ * will get called whenever the node's configuration changes. ++ * ++ * @param listener The handler for the event ++ */ ++ public void addConfigurationListener(NodeConfigListener listener) ++ { ++ PacketListener conListener = new NodeConfigTranslator(listener); ++ configEventToListenerMap.put(listener, conListener); ++ con.addPacketListener(conListener, new EventContentFilter(EventElementType.configuration.toString())); ++ } ++ ++ /** ++ * Unregister a listener for configuration events. ++ * ++ * @param listener The handler to unregister ++ */ ++ public void removeConfigurationListener(NodeConfigListener listener) ++ { ++ PacketListener conListener = configEventToListenerMap .remove(listener); ++ ++ if (conListener != null) ++ con.removePacketListener(conListener); ++ } ++ ++ /** ++ * Register an listener for item delete events. This listener ++ * gets called whenever an item is deleted from the node. ++ * ++ * @param listener The handler for the event ++ */ ++ public void addItemDeleteListener(ItemDeleteListener listener) ++ { ++ PacketListener delListener = new ItemDeleteTranslator(listener); ++ itemDeleteToListenerMap.put(listener, delListener); ++ EventContentFilter deleteItem = new EventContentFilter(EventElementType.items.toString(), "retract"); ++ EventContentFilter purge = new EventContentFilter(EventElementType.purge.toString()); ++ ++ con.addPacketListener(delListener, new OrFilter(deleteItem, purge)); ++ } ++ ++ /** ++ * Unregister a listener for item delete events. ++ * ++ * @param listener The handler to unregister ++ */ ++ public void removeItemDeleteListener(ItemDeleteListener listener) ++ { ++ PacketListener conListener = itemDeleteToListenerMap .remove(listener); ++ ++ if (conListener != null) ++ con.removePacketListener(conListener); ++ } ++ ++ @Override ++ public String toString() ++ { ++ return super.toString() + " " + getClass().getName() + " id: " + id; ++ } ++ ++ protected PubSub createPubsubPacket(Type type, PacketExtension ext) ++ { ++ return createPubsubPacket(type, ext, null); ++ } ++ ++ protected PubSub createPubsubPacket(Type type, PacketExtension ext, PubSubNamespace ns) ++ { ++ return PubSubManager.createPubsubPacket(to, type, ext, ns); ++ } ++ ++ protected Packet sendPubsubPacket(Type type, NodeExtension ext) ++ throws XMPPException ++ { ++ return PubSubManager.sendPubsubPacket(con, to, type, ext); ++ } ++ ++ protected Packet sendPubsubPacket(Type type, NodeExtension ext, PubSubNamespace ns) ++ throws XMPPException ++ { ++ return PubSubManager.sendPubsubPacket(con, to, type, ext, ns); ++ } ++ ++ ++ private static List getSubscriptionIds(Packet packet) ++ { ++ HeadersExtension headers = (HeadersExtension)packet.getExtension("headers", "http://jabber.org/protocol/shim"); ++ List values = null; ++ ++ if (headers != null) ++ { ++ values = new ArrayList(headers.getHeaders().size()); ++ ++ for (Header header : headers.getHeaders()) ++ { ++ values.add(header.getValue()); ++ } ++ } ++ return values; ++ } ++ ++ /** ++ * This class translates low level item publication events into api level objects for ++ * user consumption. ++ * ++ * @author Robin Collier ++ */ ++ public class ItemEventTranslator implements PacketListener ++ { ++ private ItemEventListener listener; ++ ++ public ItemEventTranslator(ItemEventListener eventListener) ++ { ++ listener = eventListener; ++ } ++ ++ public void processPacket(Packet packet) ++ { ++ EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns()); ++ ItemsExtension itemsElem = (ItemsExtension)event.getEvent(); ++ DelayInformation delay = (DelayInformation)packet.getExtension("delay", "urn:xmpp:delay"); ++ ++ // If there was no delay based on XEP-0203, then try XEP-0091 for backward compatibility ++ if (delay == null) ++ { ++ delay = (DelayInformation)packet.getExtension("x", "jabber:x:delay"); ++ } ++ ItemPublishEvent eventItems = new ItemPublishEvent(itemsElem.getNode(), (List)itemsElem.getItems(), getSubscriptionIds(packet), (delay == null ? null : delay.getStamp())); ++ listener.handlePublishedItems(eventItems); ++ } ++ } ++ ++ /** ++ * This class translates low level item deletion events into api level objects for ++ * user consumption. ++ * ++ * @author Robin Collier ++ */ ++ public class ItemDeleteTranslator implements PacketListener ++ { ++ private ItemDeleteListener listener; ++ ++ public ItemDeleteTranslator(ItemDeleteListener eventListener) ++ { ++ listener = eventListener; ++ } ++ ++ public void processPacket(Packet packet) ++ { ++ EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns()); ++ ++ List extList = event.getExtensions(); ++ ++ if (extList.get(0).getElementName().equals(PubSubElementType.PURGE_EVENT.getElementName())) ++ { ++ listener.handlePurge(); ++ } ++ else ++ { ++ ItemsExtension itemsElem = (ItemsExtension)event.getEvent(); ++ Collection pubItems = itemsElem.getItems(); ++ Iterator it = (Iterator)pubItems.iterator(); ++ List items = new ArrayList(pubItems.size()); ++ ++ while (it.hasNext()) ++ { ++ RetractItem item = it.next(); ++ items.add(item.getId()); ++ } ++ ++ ItemDeleteEvent eventItems = new ItemDeleteEvent(itemsElem.getNode(), items, getSubscriptionIds(packet)); ++ listener.handleDeletedItems(eventItems); ++ } ++ } ++ } ++ ++ /** ++ * This class translates low level node configuration events into api level objects for ++ * user consumption. ++ * ++ * @author Robin Collier ++ */ ++ public class NodeConfigTranslator implements PacketListener ++ { ++ private NodeConfigListener listener; ++ ++ public NodeConfigTranslator(NodeConfigListener eventListener) ++ { ++ listener = eventListener; ++ } ++ ++ public void processPacket(Packet packet) ++ { ++ EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns()); ++ ConfigurationEvent config = (ConfigurationEvent)event.getEvent(); ++ ++ listener.handleNodeConfiguration(config); ++ } ++ } ++ ++ /** ++ * Filter for {@link PacketListener} to filter out events not specific to the ++ * event type expected for this node. ++ * ++ * @author Robin Collier ++ */ ++ class EventContentFilter implements PacketFilter ++ { ++ private String firstElement; ++ private String secondElement; ++ ++ EventContentFilter(String elementName) ++ { ++ firstElement = elementName; ++ } ++ ++ EventContentFilter(String firstLevelEelement, String secondLevelElement) ++ { ++ firstElement = firstLevelEelement; ++ secondElement = secondLevelElement; ++ } ++ ++ public boolean accept(Packet packet) ++ { ++ if (!(packet instanceof Message)) ++ return false; ++ ++ EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns()); ++ ++ if (event == null) ++ return false; ++ ++ NodeExtension embedEvent = event.getEvent(); ++ ++ if (embedEvent == null) ++ return false; ++ ++ if (embedEvent.getElementName().equals(firstElement)) ++ { ++ if (!embedEvent.getNode().equals(getId())) ++ return false; ++ ++ if (secondElement == null) ++ return true; ++ ++ if (embedEvent instanceof EmbeddedPacketExtension) ++ { ++ List secondLevelList = ((EmbeddedPacketExtension)embedEvent).getExtensions(); ++ ++ if (secondLevelList.size() > 0 && secondLevelList.get(0).getElementName().equals(secondElement)) ++ return true; ++ } ++ } ++ return false; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/NodeEvent.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/NodeEvent.java +new file mode 100644 +index 0000000..c83204f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/NodeEvent.java +@@ -0,0 +1,19 @@ ++/* ++ * Created on 2009-05-12 ++ */ ++package org.jivesoftware.smackx.pubsub; ++ ++abstract public class NodeEvent ++{ ++ private String nodeId; ++ ++ protected NodeEvent(String id) ++ { ++ nodeId = id; ++ } ++ ++ public String getNodeId() ++ { ++ return nodeId; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/NodeExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/NodeExtension.java +new file mode 100644 +index 0000000..7e4cdec +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/NodeExtension.java +@@ -0,0 +1,85 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++/** ++ * A class which represents a common element within the pubsub defined ++ * schemas. One which has a node as an attribute. This class is ++ * used on its own as well as a base class for many others, since the ++ * node is a central concept to most pubsub functionality. ++ * ++ * @author Robin Collier ++ */ ++public class NodeExtension implements PacketExtension ++{ ++ private PubSubElementType element; ++ private String node; ++ ++ /** ++ * Constructs a NodeExtension with an element name specified ++ * by {@link PubSubElementType} and the specified node id. ++ * ++ * @param elem Defines the element name and namespace ++ * @param nodeId Specifies the id of the node ++ */ ++ public NodeExtension(PubSubElementType elem, String nodeId) ++ { ++ element = elem; ++ this.node = nodeId; ++ } ++ ++ /** ++ * Constructs a NodeExtension with an element name specified ++ * by {@link PubSubElementType}. ++ * ++ * @param elem Defines the element name and namespace ++ */ ++ public NodeExtension(PubSubElementType elem) ++ { ++ this(elem, null); ++ } ++ ++ /** ++ * Gets the node id ++ * ++ * @return The node id ++ */ ++ public String getNode() ++ { ++ return node; ++ } ++ ++ public String getElementName() ++ { ++ return element.getElementName(); ++ } ++ ++ public String getNamespace() ++ { ++ return element.getNamespace().getXmlns(); ++ } ++ ++ public String toXML() ++ { ++ return '<' + getElementName() + (node == null ? "" : " node='" + node + '\'') + "/>"; ++ } ++ ++ @Override ++ public String toString() ++ { ++ return getClass().getName() + " - content [" + toXML() + "]"; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/NodeType.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/NodeType.java +new file mode 100644 +index 0000000..5ee5a05 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/NodeType.java +@@ -0,0 +1,25 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++/** ++ * Defines the available types of nodes ++ * ++ * @author Robin Collier ++ */ ++public enum NodeType ++{ ++ leaf, ++ collection; ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/OptionsExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/OptionsExtension.java +new file mode 100644 +index 0000000..32c0331 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/OptionsExtension.java +@@ -0,0 +1,72 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import org.jivesoftware.smackx.pubsub.util.XmlUtils; ++ ++/** ++ * A packet extension representing the options element. ++ * ++ * @author Robin Collier ++ */ ++public class OptionsExtension extends NodeExtension ++{ ++ protected String jid; ++ protected String id; ++ ++ public OptionsExtension(String subscriptionJid) ++ { ++ this(subscriptionJid, null, null); ++ } ++ ++ public OptionsExtension(String subscriptionJid, String nodeId) ++ { ++ this(subscriptionJid, nodeId, null); ++ } ++ ++ public OptionsExtension(String jid, String nodeId, String subscriptionId) ++ { ++ super(PubSubElementType.OPTIONS, nodeId); ++ this.jid = jid; ++ id = subscriptionId; ++ } ++ ++ public String getJid() ++ { ++ return jid; ++ } ++ ++ public String getId() ++ { ++ return id; ++ } ++ ++ @Override ++ public String toXML() ++ { ++ StringBuilder builder = new StringBuilder("<"); ++ builder.append(getElementName()); ++ XmlUtils.appendAttribute(builder, "jid", jid); ++ ++ if (getNode() != null) ++ XmlUtils.appendAttribute(builder, "node", getNode()); ++ ++ if (id != null) ++ XmlUtils.appendAttribute(builder, "subid", id); ++ ++ builder.append("/>"); ++ return builder.toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PayloadItem.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PayloadItem.java +new file mode 100644 +index 0000000..488fd97 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PayloadItem.java +@@ -0,0 +1,138 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import org.jivesoftware.smack.packet.Message; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.pubsub.provider.ItemProvider; ++ ++/** ++ * This class represents an item that has been, or will be published to a ++ * pubsub node. An Item has several properties that are dependent ++ * on the configuration of the node to which it has been or will be published. ++ * ++ *

      An Item received from a node (via {@link LeafNode#getItems()} or {@link LeafNode#addItemEventListener(org.jivesoftware.smackx.pubsub.listener.ItemEventListener)} ++ *
    • Will always have an id (either user or server generated) unless node configuration has both ++ * {@link ConfigureForm#isPersistItems()} and {@link ConfigureForm#isDeliverPayloads()}set to false. ++ *
    • Will have a payload if the node configuration has {@link ConfigureForm#isDeliverPayloads()} set ++ * to true, otherwise it will be null. ++ * ++ *

      An Item created to send to a node (via {@link LeafNode#send()} or {@link LeafNode#publish()} ++ *
    • The id is optional, since the server will generate one if necessary, but should be used if it is ++ * meaningful in the context of the node. This value must be unique within the node that it is sent to, since ++ * resending an item with the same id will overwrite the one that already exists if the items are persisted. ++ *
    • Will require payload if the node configuration has {@link ConfigureForm#isDeliverPayloads()} set ++ * to true. ++ * ++ *

      To customise the payload object being returned from the {@link #getPayload()} method, you can ++ * add a custom parser as explained in {@link ItemProvider}. ++ * ++ * @author Robin Collier ++ */ ++public class PayloadItem extends Item ++{ ++ private E payload; ++ ++ /** ++ * Create an Item with no id and a payload The id will be set by the server. ++ * ++ * @param payloadExt A {@link PacketExtension} which represents the payload data. ++ */ ++ public PayloadItem(E payloadExt) ++ { ++ super(); ++ ++ if (payloadExt == null) ++ throw new IllegalArgumentException("payload cannot be 'null'"); ++ payload = payloadExt; ++ } ++ ++ /** ++ * Create an Item with an id and payload. ++ * ++ * @param itemId The id of this item. It can be null if we want the server to set the id. ++ * @param payloadExt A {@link PacketExtension} which represents the payload data. ++ */ ++ public PayloadItem(String itemId, E payloadExt) ++ { ++ super(itemId); ++ ++ if (payloadExt == null) ++ throw new IllegalArgumentException("payload cannot be 'null'"); ++ payload = payloadExt; ++ } ++ ++ /** ++ * Create an Item with an id, node id and payload. ++ * ++ *

      ++ * Note: This is not valid for publishing an item to a node, only receiving from ++ * one as part of {@link Message}. If used to create an Item to publish ++ * (via {@link LeafNode#publish(Item)}, the server may return an ++ * error for an invalid packet. ++ * ++ * @param itemId The id of this item. ++ * @param nodeId The id of the node the item was published to. ++ * @param payloadExt A {@link PacketExtension} which represents the payload data. ++ */ ++ public PayloadItem(String itemId, String nodeId, E payloadExt) ++ { ++ super(itemId, nodeId); ++ ++ if (payloadExt == null) ++ throw new IllegalArgumentException("payload cannot be 'null'"); ++ payload = payloadExt; ++ } ++ ++ /** ++ * Get the payload associated with this Item. Customising the payload ++ * parsing from the server can be accomplished as described in {@link ItemProvider}. ++ * ++ * @return The payload as a {@link PacketExtension}. ++ */ ++ public E getPayload() ++ { ++ return payload; ++ } ++ ++ @Override ++ public String toXML() ++ { ++ StringBuilder builder = new StringBuilder(""); ++ builder.append(payload.toXML()); ++ builder.append(""); ++ ++ return builder.toString(); ++ } ++ ++ @Override ++ public String toString() ++ { ++ return getClass().getName() + " | Content [" + toXML() + "]"; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PresenceState.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PresenceState.java +new file mode 100644 +index 0000000..0612fc2 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PresenceState.java +@@ -0,0 +1,25 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++/** ++ * Defines the possible valid presence states for node subscription via ++ * {@link SubscribeForm#getShowValues()}. ++ * ++ * @author Robin Collier ++ */ ++public enum PresenceState ++{ ++ chat, online, away, xa, dnd ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PubSubElementType.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PubSubElementType.java +new file mode 100644 +index 0000000..a887ca2 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PubSubElementType.java +@@ -0,0 +1,80 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; ++ ++/** ++ * Defines all the possible element types as defined for all the pubsub ++ * schemas in all 3 namespaces. ++ * ++ * @author Robin Collier ++ */ ++public enum PubSubElementType ++{ ++ CREATE("create", PubSubNamespace.BASIC), ++ DELETE("delete", PubSubNamespace.OWNER), ++ DELETE_EVENT("delete", PubSubNamespace.EVENT), ++ CONFIGURE("configure", PubSubNamespace.BASIC), ++ CONFIGURE_OWNER("configure", PubSubNamespace.OWNER), ++ CONFIGURATION("configuration", PubSubNamespace.EVENT), ++ OPTIONS("options", PubSubNamespace.BASIC), ++ DEFAULT("default", PubSubNamespace.OWNER), ++ ITEMS("items", PubSubNamespace.BASIC), ++ ITEMS_EVENT("items", PubSubNamespace.EVENT), ++ ITEM("item", PubSubNamespace.BASIC), ++ ITEM_EVENT("item", PubSubNamespace.EVENT), ++ PUBLISH("publish", PubSubNamespace.BASIC), ++ PUBLISH_OPTIONS("publish-options", PubSubNamespace.BASIC), ++ PURGE_OWNER("purge", PubSubNamespace.OWNER), ++ PURGE_EVENT("purge", PubSubNamespace.EVENT), ++ RETRACT("retract", PubSubNamespace.BASIC), ++ AFFILIATIONS("affiliations", PubSubNamespace.BASIC), ++ SUBSCRIBE("subscribe", PubSubNamespace.BASIC), ++ SUBSCRIPTION("subscription", PubSubNamespace.BASIC), ++ SUBSCRIPTIONS("subscriptions", PubSubNamespace.BASIC), ++ UNSUBSCRIBE("unsubscribe", PubSubNamespace.BASIC); ++ ++ private String eName; ++ private PubSubNamespace nSpace; ++ ++ private PubSubElementType(String elemName, PubSubNamespace ns) ++ { ++ eName = elemName; ++ nSpace = ns; ++ } ++ ++ public PubSubNamespace getNamespace() ++ { ++ return nSpace; ++ } ++ ++ public String getElementName() ++ { ++ return eName; ++ } ++ ++ public static PubSubElementType valueOfFromElemName(String elemName, String namespace) ++ { ++ int index = namespace.lastIndexOf('#'); ++ String fragment = (index == -1 ? null : namespace.substring(index+1)); ++ ++ if (fragment != null) ++ { ++ return valueOf((elemName + '_' + fragment).toUpperCase()); ++ } ++ return valueOf(elemName.toUpperCase().replace('-', '_')); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PubSubManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PubSubManager.java +new file mode 100644 +index 0000000..e3eeae4 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PubSubManager.java +@@ -0,0 +1,329 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import java.util.List; ++import java.util.Map; ++import java.util.concurrent.ConcurrentHashMap; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.packet.IQ.Type; ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.FormField; ++import org.jivesoftware.smackx.ServiceDiscoveryManager; ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++import org.jivesoftware.smackx.packet.DiscoverItems; ++import org.jivesoftware.smackx.pubsub.packet.PubSub; ++import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; ++import org.jivesoftware.smackx.pubsub.packet.SyncPacketSend; ++import org.jivesoftware.smackx.pubsub.util.NodeUtils; ++ ++/** ++ * This is the starting point for access to the pubsub service. It ++ * will provide access to general information about the service, as ++ * well as create or retrieve pubsub {@link LeafNode} instances. These ++ * instances provide the bulk of the functionality as defined in the ++ * pubsub specification XEP-0060. ++ * ++ * @author Robin Collier ++ */ ++final public class PubSubManager ++{ ++ private Connection con; ++ private String to; ++ private Map nodeMap = new ConcurrentHashMap(); ++ ++ /** ++ * Create a pubsub manager associated to the specified connection. Defaults the service ++ * name to pubsub ++ * ++ * @param connection The XMPP connection ++ */ ++ public PubSubManager(Connection connection) ++ { ++ con = connection; ++ to = "pubsub." + connection.getServiceName(); ++ } ++ ++ /** ++ * Create a pubsub manager associated to the specified connection where ++ * the pubsub requests require a specific to address for packets. ++ * ++ * @param connection The XMPP connection ++ * @param toAddress The pubsub specific to address (required for some servers) ++ */ ++ public PubSubManager(Connection connection, String toAddress) ++ { ++ con = connection; ++ to = toAddress; ++ } ++ ++ /** ++ * Creates an instant node, if supported. ++ * ++ * @return The node that was created ++ * @exception XMPPException ++ */ ++ public LeafNode createNode() ++ throws XMPPException ++ { ++ PubSub reply = (PubSub)sendPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.CREATE)); ++ NodeExtension elem = (NodeExtension)reply.getExtension("create", PubSubNamespace.BASIC.getXmlns()); ++ ++ LeafNode newNode = new LeafNode(con, elem.getNode()); ++ newNode.setTo(to); ++ nodeMap.put(newNode.getId(), newNode); ++ ++ return newNode; ++ } ++ ++ /** ++ * Creates a node with default configuration. ++ * ++ * @param id The id of the node, which must be unique within the ++ * pubsub service ++ * @return The node that was created ++ * @exception XMPPException ++ */ ++ public LeafNode createNode(String id) ++ throws XMPPException ++ { ++ return (LeafNode)createNode(id, null); ++ } ++ ++ /** ++ * Creates a node with specified configuration. ++ * ++ * Note: This is the only way to create a collection node. ++ * ++ * @param name The name of the node, which must be unique within the ++ * pubsub service ++ * @param config The configuration for the node ++ * @return The node that was created ++ * @exception XMPPException ++ */ ++ public Node createNode(String name, Form config) ++ throws XMPPException ++ { ++ PubSub request = createPubsubPacket(to, Type.SET, new NodeExtension(PubSubElementType.CREATE, name)); ++ boolean isLeafNode = true; ++ ++ if (config != null) ++ { ++ request.addExtension(new FormNode(FormNodeType.CONFIGURE, config)); ++ FormField nodeTypeField = config.getField(ConfigureNodeFields.node_type.getFieldName()); ++ ++ if (nodeTypeField != null) ++ isLeafNode = nodeTypeField.getValues().next().equals(NodeType.leaf.toString()); ++ } ++ ++ // Errors will cause exceptions in getReply, so it only returns ++ // on success. ++ sendPubsubPacket(con, to, Type.SET, request); ++ Node newNode = isLeafNode ? new LeafNode(con, name) : new CollectionNode(con, name); ++ newNode.setTo(to); ++ nodeMap.put(newNode.getId(), newNode); ++ ++ return newNode; ++ } ++ ++ /** ++ * Retrieves the requested node, if it exists. It will throw an ++ * exception if it does not. ++ * ++ * @param id - The unique id of the node ++ * @return the node ++ * @throws XMPPException The node does not exist ++ */ ++ public Node getNode(String id) ++ throws XMPPException ++ { ++ Node node = nodeMap.get(id); ++ ++ if (node == null) ++ { ++ DiscoverInfo info = new DiscoverInfo(); ++ info.setTo(to); ++ info.setNode(id); ++ ++ DiscoverInfo infoReply = (DiscoverInfo)SyncPacketSend.getReply(con, info); ++ ++ if (infoReply.getIdentities().next().getType().equals(NodeType.leaf.toString())) ++ node = new LeafNode(con, id); ++ else ++ node = new CollectionNode(con, id); ++ node.setTo(to); ++ nodeMap.put(id, node); ++ } ++ return node; ++ } ++ ++ /** ++ * Get all the nodes that currently exist as a child of the specified ++ * collection node. If the service does not support collection nodes ++ * then all nodes will be returned. ++ * ++ * To retrieve contents of the root collection node (if it exists), ++ * or there is no root collection node, pass null as the nodeId. ++ * ++ * @param nodeId - The id of the collection node for which the child ++ * nodes will be returned. ++ * @return {@link DiscoverItems} representing the existing nodes ++ * ++ * @throws XMPPException ++ */ ++ public DiscoverItems discoverNodes(String nodeId) ++ throws XMPPException ++ { ++ DiscoverItems items = new DiscoverItems(); ++ ++ if (nodeId != null) ++ items.setNode(nodeId); ++ items.setTo(to); ++ DiscoverItems nodeItems = (DiscoverItems)SyncPacketSend.getReply(con, items); ++ return nodeItems; ++ } ++ ++ /** ++ * Gets the subscriptions on the root node. ++ * ++ * @return List of exceptions ++ * ++ * @throws XMPPException ++ */ ++ public List getSubscriptions() ++ throws XMPPException ++ { ++ Packet reply = sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.SUBSCRIPTIONS)); ++ SubscriptionsExtension subElem = (SubscriptionsExtension)reply.getExtension(PubSubElementType.SUBSCRIPTIONS.getElementName(), PubSubElementType.SUBSCRIPTIONS.getNamespace().getXmlns()); ++ return subElem.getSubscriptions(); ++ } ++ ++ /** ++ * Gets the affiliations on the root node. ++ * ++ * @return List of affiliations ++ * ++ * @throws XMPPException ++ */ ++ public List getAffiliations() ++ throws XMPPException ++ { ++ PubSub reply = (PubSub)sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.AFFILIATIONS)); ++ AffiliationsExtension listElem = (AffiliationsExtension)reply.getExtension(PubSubElementType.AFFILIATIONS); ++ return listElem.getAffiliations(); ++ } ++ ++ /** ++ * Delete the specified node ++ * ++ * @param nodeId ++ * @throws XMPPException ++ */ ++ public void deleteNode(String nodeId) ++ throws XMPPException ++ { ++ sendPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.DELETE, nodeId), PubSubElementType.DELETE.getNamespace()); ++ nodeMap.remove(nodeId); ++ } ++ ++ /** ++ * Returns the default settings for Node configuration. ++ * ++ * @return configuration form containing the default settings. ++ */ ++ public ConfigureForm getDefaultConfiguration() ++ throws XMPPException ++ { ++ // Errors will cause exceptions in getReply, so it only returns ++ // on success. ++ PubSub reply = (PubSub)sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.DEFAULT), PubSubElementType.DEFAULT.getNamespace()); ++ return NodeUtils.getFormFromPacket(reply, PubSubElementType.DEFAULT); ++ } ++ ++ /** ++ * Gets the supported features of the servers pubsub implementation ++ * as a standard {@link DiscoverInfo} instance. ++ * ++ * @return The supported features ++ * ++ * @throws XMPPException ++ */ ++ public DiscoverInfo getSupportedFeatures() ++ throws XMPPException ++ { ++ ServiceDiscoveryManager mgr = ServiceDiscoveryManager.getInstanceFor(con); ++ return mgr.discoverInfo(to); ++ } ++ ++ private Packet sendPubsubPacket(Type type, PacketExtension ext, PubSubNamespace ns) ++ throws XMPPException ++ { ++ return sendPubsubPacket(con, to, type, ext, ns); ++ } ++ ++ private Packet sendPubsubPacket(Type type, PacketExtension ext) ++ throws XMPPException ++ { ++ return sendPubsubPacket(type, ext, null); ++ } ++ ++ static PubSub createPubsubPacket(String to, Type type, PacketExtension ext) ++ { ++ return createPubsubPacket(to, type, ext, null); ++ } ++ ++ static PubSub createPubsubPacket(String to, Type type, PacketExtension ext, PubSubNamespace ns) ++ { ++ PubSub request = new PubSub(); ++ request.setTo(to); ++ request.setType(type); ++ ++ if (ns != null) ++ { ++ request.setPubSubNamespace(ns); ++ } ++ request.addExtension(ext); ++ ++ return request; ++ } ++ ++ static Packet sendPubsubPacket(Connection con, String to, Type type, PacketExtension ext) ++ throws XMPPException ++ { ++ return sendPubsubPacket(con, to, type, ext, null); ++ } ++ ++ static Packet sendPubsubPacket(Connection con, String to, Type type, PacketExtension ext, PubSubNamespace ns) ++ throws XMPPException ++ { ++ return SyncPacketSend.getReply(con, createPubsubPacket(to, type, ext, ns)); ++ } ++ ++ static Packet sendPubsubPacket(Connection con, String to, Type type, PubSub packet) ++ throws XMPPException ++ { ++ return sendPubsubPacket(con, to, type, packet, null); ++ } ++ ++ static Packet sendPubsubPacket(Connection con, String to, Type type, PubSub packet, PubSubNamespace ns) ++ throws XMPPException ++ { ++ return SyncPacketSend.getReply(con, packet); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PublishItem.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PublishItem.java +new file mode 100644 +index 0000000..ffbd705 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PublishItem.java +@@ -0,0 +1,70 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import java.util.ArrayList; ++import java.util.Collection; ++ ++/** ++ * Represents a request to publish an item(s) to a specific node. ++ * ++ * @author Robin Collier ++ */ ++public class PublishItem extends NodeExtension ++{ ++ protected Collection items; ++ ++ /** ++ * Construct a request to publish an item to a node. ++ * ++ * @param nodeId The node to publish to ++ * @param toPublish The {@link Item} to publish ++ */ ++ public PublishItem(String nodeId, T toPublish) ++ { ++ super(PubSubElementType.PUBLISH, nodeId); ++ items = new ArrayList(1); ++ items.add(toPublish); ++ } ++ ++ /** ++ * Construct a request to publish multiple items to a node. ++ * ++ * @param nodeId The node to publish to ++ * @param toPublish The list of {@link Item} to publish ++ */ ++ public PublishItem(String nodeId, Collection toPublish) ++ { ++ super(PubSubElementType.PUBLISH, nodeId); ++ items = toPublish; ++ } ++ ++ @Override ++ public String toXML() ++ { ++ StringBuilder builder = new StringBuilder("<"); ++ builder.append(getElementName()); ++ builder.append(" node='"); ++ builder.append(getNode()); ++ builder.append("'>"); ++ ++ for (Item item : items) ++ { ++ builder.append(item.toXML()); ++ } ++ builder.append(""); ++ ++ return builder.toString(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PublishModel.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PublishModel.java +new file mode 100644 +index 0000000..4b5a851 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PublishModel.java +@@ -0,0 +1,32 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++/** ++ * Determines who may publish to a node. Denotes possible values ++ * for {@link ConfigureForm#setPublishModel(PublishModel)}. ++ * ++ * @author Robin Collier ++ */ ++public enum PublishModel ++{ ++ /** Only publishers may publish */ ++ publishers, ++ ++ /** Only subscribers may publish */ ++ subscribers, ++ ++ /** Anyone may publish */ ++ open; ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/RetractItem.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/RetractItem.java +new file mode 100644 +index 0000000..97db5cc +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/RetractItem.java +@@ -0,0 +1,59 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; ++ ++/** ++ * Represents and item that has been deleted from a node. ++ * ++ * @author Robin Collier ++ */ ++public class RetractItem implements PacketExtension ++{ ++ private String id; ++ ++ /** ++ * Construct a RetractItem with the specified id. ++ * ++ * @param itemId The id if the item deleted ++ */ ++ public RetractItem(String itemId) ++ { ++ if (itemId == null) ++ throw new IllegalArgumentException("itemId must not be 'null'"); ++ id = itemId; ++ } ++ ++ public String getId() ++ { ++ return id; ++ } ++ ++ public String getElementName() ++ { ++ return "retract"; ++ } ++ ++ public String getNamespace() ++ { ++ return PubSubNamespace.EVENT.getXmlns(); ++ } ++ ++ public String toXML() ++ { ++ return ""; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SimplePayload.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SimplePayload.java +new file mode 100644 +index 0000000..9d114b0 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SimplePayload.java +@@ -0,0 +1,65 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++/** ++ * The default payload representation for {@link Item#getPayload()}. It simply ++ * stores the XML payload as a string. ++ * ++ * @author Robin Collier ++ */ ++public class SimplePayload implements PacketExtension ++{ ++ private String elemName; ++ private String ns; ++ private String payload; ++ ++ /** ++ * Construct a SimplePayload object with the specified element name, ++ * namespace and content. The content must be well formed XML. ++ * ++ * @param elementName The root element name (of the payload) ++ * @param namespace The namespace of the payload, null if there is none ++ * @param xmlPayload The payload data ++ */ ++ public SimplePayload(String elementName, String namespace, String xmlPayload) ++ { ++ elemName = elementName; ++ payload = xmlPayload; ++ ns = namespace; ++ } ++ ++ public String getElementName() ++ { ++ return elemName; ++ } ++ ++ public String getNamespace() ++ { ++ return ns; ++ } ++ ++ public String toXML() ++ { ++ return payload; ++ } ++ ++ @Override ++ public String toString() ++ { ++ return getClass().getName() + "payload [" + toXML() + "]"; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscribeExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscribeExtension.java +new file mode 100644 +index 0000000..daf8db7 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscribeExtension.java +@@ -0,0 +1,60 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++/** ++ * Represents a request to subscribe to a node. ++ * ++ * @author Robin Collier ++ */ ++public class SubscribeExtension extends NodeExtension ++{ ++ protected String jid; ++ ++ public SubscribeExtension(String subscribeJid) ++ { ++ super(PubSubElementType.SUBSCRIBE); ++ jid = subscribeJid; ++ } ++ ++ public SubscribeExtension(String subscribeJid, String nodeId) ++ { ++ super(PubSubElementType.SUBSCRIBE, nodeId); ++ jid = subscribeJid; ++ } ++ ++ public String getJid() ++ { ++ return jid; ++ } ++ ++ @Override ++ public String toXML() ++ { ++ StringBuilder builder = new StringBuilder("<"); ++ builder.append(getElementName()); ++ ++ if (getNode() != null) ++ { ++ builder.append(" node='"); ++ builder.append(getNode()); ++ builder.append("'"); ++ } ++ builder.append(" jid='"); ++ builder.append(getJid()); ++ builder.append("'/>"); ++ ++ return builder.toString(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscribeForm.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscribeForm.java +new file mode 100644 +index 0000000..5ed5311 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscribeForm.java +@@ -0,0 +1,242 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import java.text.ParseException; ++import java.text.SimpleDateFormat; ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.Date; ++import java.util.Iterator; ++import java.util.UnknownFormatConversionException; ++ ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.FormField; ++import org.jivesoftware.smackx.packet.DataForm; ++ ++/** ++ * A decorator for a {@link Form} to easily enable reading and updating ++ * of subscription options. All operations read or update the underlying {@link DataForm}. ++ * ++ *

      Unlike the {@link Form}.setAnswer(XXX)} methods, which throw an exception if the field does not ++ * exist, all SubscribeForm.setXXX methods will create the field in the wrapped form ++ * if it does not already exist. ++ * ++ * @author Robin Collier ++ */ ++public class SubscribeForm extends Form ++{ ++ private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); ++ ++ public SubscribeForm(DataForm configDataForm) ++ { ++ super(configDataForm); ++ } ++ ++ public SubscribeForm(Form subscribeOptionsForm) ++ { ++ super(subscribeOptionsForm.getDataFormToSend()); ++ } ++ ++ public SubscribeForm(FormType formType) ++ { ++ super(formType.toString()); ++ } ++ ++ /** ++ * Determines if an entity wants to receive notifications. ++ * ++ * @return true if want to receive, false otherwise ++ */ ++ public boolean isDeliverOn() ++ { ++ return parseBoolean(getFieldValue(SubscribeOptionFields.deliver)); ++ } ++ ++ /** ++ * Sets whether an entity wants to receive notifications. ++ * ++ * @param deliverNotifications ++ */ ++ public void setDeliverOn(boolean deliverNotifications) ++ { ++ addField(SubscribeOptionFields.deliver, FormField.TYPE_BOOLEAN); ++ setAnswer(SubscribeOptionFields.deliver.getFieldName(), deliverNotifications); ++ } ++ ++ /** ++ * Determines if notifications should be delivered as aggregations or not. ++ * ++ * @return true to aggregate, false otherwise ++ */ ++ public boolean isDigestOn() ++ { ++ return parseBoolean(getFieldValue(SubscribeOptionFields.digest)); ++ } ++ ++ /** ++ * Sets whether notifications should be delivered as aggregations or not. ++ * ++ * @param digestOn true to aggregate, false otherwise ++ */ ++ public void setDigestOn(boolean digestOn) ++ { ++ addField(SubscribeOptionFields.deliver, FormField.TYPE_BOOLEAN); ++ setAnswer(SubscribeOptionFields.deliver.getFieldName(), digestOn); ++ } ++ ++ /** ++ * Gets the minimum number of milliseconds between sending notification digests ++ * ++ * @return The frequency in milliseconds ++ */ ++ public int getDigestFrequency() ++ { ++ return Integer.parseInt(getFieldValue(SubscribeOptionFields.digest_frequency)); ++ } ++ ++ /** ++ * Sets the minimum number of milliseconds between sending notification digests ++ * ++ * @param frequency The frequency in milliseconds ++ */ ++ public void setDigestFrequency(int frequency) ++ { ++ addField(SubscribeOptionFields.digest_frequency, FormField.TYPE_TEXT_SINGLE); ++ setAnswer(SubscribeOptionFields.digest_frequency.getFieldName(), frequency); ++ } ++ ++ /** ++ * Get the time at which the leased subscription will expire, or has expired. ++ * ++ * @return The expiry date ++ */ ++ public Date getExpiry() ++ { ++ String dateTime = getFieldValue(SubscribeOptionFields.expire); ++ try ++ { ++ return format.parse(dateTime); ++ } ++ catch (ParseException e) ++ { ++ UnknownFormatConversionException exc = new UnknownFormatConversionException(dateTime); ++ exc.initCause(e); ++ throw exc; ++ } ++ } ++ ++ /** ++ * Sets the time at which the leased subscription will expire, or has expired. ++ * ++ * @param expire The expiry date ++ */ ++ public void setExpiry(Date expire) ++ { ++ addField(SubscribeOptionFields.expire, FormField.TYPE_TEXT_SINGLE); ++ setAnswer(SubscribeOptionFields.expire.getFieldName(), format.format(expire)); ++ } ++ ++ /** ++ * Determines whether the entity wants to receive an XMPP message body in ++ * addition to the payload format. ++ * ++ * @return true to receive the message body, false otherwise ++ */ ++ public boolean isIncludeBody() ++ { ++ return parseBoolean(getFieldValue(SubscribeOptionFields.include_body)); ++ } ++ ++ /** ++ * Sets whether the entity wants to receive an XMPP message body in ++ * addition to the payload format. ++ * ++ * @param include true to receive the message body, false otherwise ++ */ ++ public void setIncludeBody(boolean include) ++ { ++ addField(SubscribeOptionFields.include_body, FormField.TYPE_BOOLEAN); ++ setAnswer(SubscribeOptionFields.include_body.getFieldName(), include); ++ } ++ ++ /** ++ * Gets the {@link PresenceState} for which an entity wants to receive ++ * notifications. ++ * ++ * @return iterator over the list of states ++ */ ++ public Iterator getShowValues() ++ { ++ ArrayList result = new ArrayList(5); ++ Iterator it = getFieldValues(SubscribeOptionFields.show_values); ++ ++ while (it.hasNext()) ++ { ++ String state = it.next(); ++ result.add(PresenceState.valueOf(state)); ++ } ++ return result.iterator(); ++ } ++ ++ /** ++ * Sets the list of {@link PresenceState} for which an entity wants ++ * to receive notifications. ++ * ++ * @param stateValues The list of states ++ */ ++ public void setShowValues(Collection stateValues) ++ { ++ ArrayList values = new ArrayList(stateValues.size()); ++ ++ for (PresenceState state : stateValues) ++ { ++ values.add(state.toString()); ++ } ++ addField(SubscribeOptionFields.show_values, FormField.TYPE_LIST_MULTI); ++ setAnswer(SubscribeOptionFields.show_values.getFieldName(), values); ++ } ++ ++ ++ static private boolean parseBoolean(String fieldValue) ++ { ++ return ("1".equals(fieldValue) || "true".equals(fieldValue)); ++ } ++ ++ private String getFieldValue(SubscribeOptionFields field) ++ { ++ FormField formField = getField(field.getFieldName()); ++ ++ return formField.getValues().next(); ++ } ++ ++ private Iterator getFieldValues(SubscribeOptionFields field) ++ { ++ FormField formField = getField(field.getFieldName()); ++ ++ return formField.getValues(); ++ } ++ ++ private void addField(SubscribeOptionFields nodeField, String type) ++ { ++ String fieldName = nodeField.getFieldName(); ++ ++ if (getField(fieldName) == null) ++ { ++ FormField field = new FormField(fieldName); ++ field.setType(type); ++ addField(field); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscribeOptionFields.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscribeOptionFields.java +new file mode 100644 +index 0000000..dfca601 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscribeOptionFields.java +@@ -0,0 +1,99 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import java.util.Calendar; ++ ++/** ++ * Defines the possible field options for a subscribe options form as defined ++ * by Section 16.4.2. ++ * ++ * @author Robin Collier ++ */ ++public enum SubscribeOptionFields ++{ ++ /** ++ * Whether an entity wants to receive or disable notifications ++ * ++ *

      Value: boolean

      ++ */ ++ deliver, ++ ++ /** ++ * Whether an entity wants to receive digests (aggregations) of ++ * notifications or all notifications individually. ++ * ++ *

      Value: boolean

      ++ */ ++ digest, ++ ++ /** ++ * The minimum number of seconds between sending any two notifications digests ++ * ++ *

      Value: int

      ++ */ ++ digest_frequency, ++ ++ /** ++ * The DateTime at which a leased subsscription will end ro has ended. ++ * ++ *

      Value: {@link Calendar}

      ++ */ ++ expire, ++ ++ /** ++ * Whether an entity wants to receive an XMPP message body in addition to ++ * the payload format. ++ * ++ *

      Value: boolean

      ++ */ ++ include_body, ++ ++ /** ++ * The presence states for which an entity wants to receive notifications. ++ * ++ *

      Value: {@link PresenceState}

      ++ */ ++ show_values, ++ ++ /** ++ * ++ * ++ *

      Value:

      ++ */ ++ subscription_type, ++ ++ /** ++ * ++ *

      Value:

      ++ */ ++ subscription_depth; ++ ++ public String getFieldName() ++ { ++ if (this == show_values) ++ return "pubsub#" + toString().replace('_', '-'); ++ return "pubsub#" + toString(); ++ } ++ ++ static public SubscribeOptionFields valueOfFromElement(String elementName) ++ { ++ String portion = elementName.substring(elementName.lastIndexOf('#' + 1)); ++ ++ if ("show-values".equals(portion)) ++ return show_values; ++ else ++ return valueOf(portion); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Subscription.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Subscription.java +new file mode 100644 +index 0000000..19ad8a8 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Subscription.java +@@ -0,0 +1,160 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++/** ++ * Represents a subscription to node for both requests and replies. ++ * ++ * @author Robin Collier ++ */ ++public class Subscription extends NodeExtension ++{ ++ protected String jid; ++ protected String id; ++ protected State state; ++ protected boolean configRequired = false; ++ ++ public enum State ++ { ++ subscribed, unconfigured, pending, none ++ } ++ ++ /** ++ * Used to constructs a subscription request to the root node with the specified ++ * JID. ++ * ++ * @param subscriptionJid The subscriber JID ++ */ ++ public Subscription(String subscriptionJid) ++ { ++ this(subscriptionJid, null, null, null); ++ } ++ ++ /** ++ * Used to constructs a subscription request to the specified node with the specified ++ * JID. ++ * ++ * @param subscriptionJid The subscriber JID ++ * @param nodeId The node id ++ */ ++ public Subscription(String subscriptionJid, String nodeId) ++ { ++ this(subscriptionJid, nodeId, null, null); ++ } ++ ++ /** ++ * Constructs a representation of a subscription reply to the specified node ++ * and JID. The server will have supplied the subscription id and current state. ++ * ++ * @param jid The JID the request was made under ++ * @param nodeId The node subscribed to ++ * @param subscriptionId The id of this subscription ++ * @param state The current state of the subscription ++ */ ++ public Subscription(String jid, String nodeId, String subscriptionId, State state) ++ { ++ super(PubSubElementType.SUBSCRIPTION, nodeId); ++ this.jid = jid; ++ id = subscriptionId; ++ this.state = state; ++ } ++ ++ /** ++ * Constructs a representation of a subscription reply to the specified node ++ * and JID. The server will have supplied the subscription id and current state ++ * and whether the subscription need to be configured. ++ * ++ * @param jid The JID the request was made under ++ * @param nodeId The node subscribed to ++ * @param subscriptionId The id of this subscription ++ * @param state The current state of the subscription ++ * @param configRequired Is configuration required to complete the subscription ++ */ ++ public Subscription(String jid, String nodeId, String subscriptionId, State state, boolean configRequired) ++ { ++ super(PubSubElementType.SUBSCRIPTION, nodeId); ++ this.jid = jid; ++ id = subscriptionId; ++ this.state = state; ++ this.configRequired = configRequired; ++ } ++ ++ /** ++ * Gets the JID the subscription is created for ++ * ++ * @return The JID ++ */ ++ public String getJid() ++ { ++ return jid; ++ } ++ ++ /** ++ * Gets the subscription id ++ * ++ * @return The subscription id ++ */ ++ public String getId() ++ { ++ return id; ++ } ++ ++ /** ++ * Gets the current subscription state. ++ * ++ * @return Current subscription state ++ */ ++ public State getState() ++ { ++ return state; ++ } ++ ++ /** ++ * This value is only relevant when the {@link #getState()} is {@link State#unconfigured} ++ * ++ * @return true if configuration is required, false otherwise ++ */ ++ public boolean isConfigRequired() ++ { ++ return configRequired; ++ } ++ ++ public String toXML() ++ { ++ StringBuilder builder = new StringBuilder(""); ++ return builder.toString(); ++ } ++ ++ private void appendAttribute(StringBuilder builder, String att, String value) ++ { ++ builder.append(" "); ++ builder.append(att); ++ builder.append("='"); ++ builder.append(value); ++ builder.append("'"); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscriptionEvent.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscriptionEvent.java +new file mode 100644 +index 0000000..99f18d5 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscriptionEvent.java +@@ -0,0 +1,75 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import java.util.Collections; ++import java.util.List; ++ ++/** ++ * Base class to represents events that are associated to subscriptions. ++ * ++ * @author Robin Collier ++ */ ++abstract public class SubscriptionEvent extends NodeEvent ++{ ++ private List subIds = Collections.EMPTY_LIST; ++ ++ /** ++ * Construct an event with no subscription id's. This can ++ * occur when there is only one subscription to a node. The ++ * event may or may not report the subscription id along ++ * with the event. ++ * ++ * @param nodeId The id of the node the event came from ++ */ ++ protected SubscriptionEvent(String nodeId) ++ { ++ super(nodeId); ++ } ++ ++ /** ++ * Construct an event with multiple subscriptions. ++ * ++ * @param nodeId The id of the node the event came from ++ * @param subscriptionIds The list of subscription id's ++ */ ++ protected SubscriptionEvent(String nodeId, List subscriptionIds) ++ { ++ super(nodeId); ++ ++ if (subscriptionIds != null) ++ subIds = subscriptionIds; ++ } ++ ++ /** ++ * Get the subscriptions this event is associated with. ++ * ++ * @return List of subscription id's ++ */ ++ public List getSubscriptions() ++ { ++ return Collections.unmodifiableList(subIds); ++ } ++ ++ /** ++ * Set the list of subscription id's for this event. ++ * ++ * @param subscriptionIds The list of subscription id's ++ */ ++ protected void setSubscriptions(List subscriptionIds) ++ { ++ if (subscriptionIds != null) ++ subIds = subscriptionIds; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscriptionsExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscriptionsExtension.java +new file mode 100644 +index 0000000..a28cbe2 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscriptionsExtension.java +@@ -0,0 +1,96 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import java.util.Collections; ++import java.util.List; ++ ++/** ++ * Represents the element holding the list of subscription elements. ++ * ++ * @author Robin Collier ++ */ ++public class SubscriptionsExtension extends NodeExtension ++{ ++ protected List items = Collections.EMPTY_LIST; ++ ++ /** ++ * Subscriptions to the root node ++ * ++ * @param subList The list of subscriptions ++ */ ++ public SubscriptionsExtension(List subList) ++ { ++ super(PubSubElementType.SUBSCRIPTIONS); ++ ++ if (subList != null) ++ items = subList; ++ } ++ ++ /** ++ * Subscriptions to the specified node. ++ * ++ * @param nodeId The node subscribed to ++ * @param subList The list of subscriptions ++ */ ++ public SubscriptionsExtension(String nodeId, List subList) ++ { ++ super(PubSubElementType.SUBSCRIPTIONS, nodeId); ++ ++ if (subList != null) ++ items = subList; ++ } ++ ++ /** ++ * Gets the list of subscriptions. ++ * ++ * @return List of subscriptions ++ */ ++ public List getSubscriptions() ++ { ++ return items; ++ } ++ ++ @Override ++ public String toXML() ++ { ++ if ((items == null) || (items.size() == 0)) ++ { ++ return super.toXML(); ++ } ++ else ++ { ++ StringBuilder builder = new StringBuilder("<"); ++ builder.append(getElementName()); ++ ++ if (getNode() != null) ++ { ++ builder.append(" node='"); ++ builder.append(getNode()); ++ builder.append("'"); ++ } ++ builder.append(">"); ++ ++ for (Subscription item : items) ++ { ++ builder.append(item.toXML()); ++ } ++ ++ builder.append(""); ++ return builder.toString(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/UnsubscribeExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/UnsubscribeExtension.java +new file mode 100644 +index 0000000..ac14c60 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/UnsubscribeExtension.java +@@ -0,0 +1,73 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub; ++ ++import org.jivesoftware.smackx.pubsub.util.XmlUtils; ++ ++ ++/** ++ * Represents an unsubscribe element. ++ * ++ * @author Robin Collier ++ */ ++public class UnsubscribeExtension extends NodeExtension ++{ ++ protected String jid; ++ protected String id; ++ ++ public UnsubscribeExtension(String subscriptionJid) ++ { ++ this(subscriptionJid, null, null); ++ } ++ ++ public UnsubscribeExtension(String subscriptionJid, String nodeId) ++ { ++ this(subscriptionJid, nodeId, null); ++ } ++ ++ public UnsubscribeExtension(String jid, String nodeId, String subscriptionId) ++ { ++ super(PubSubElementType.UNSUBSCRIBE, nodeId); ++ this.jid = jid; ++ id = subscriptionId; ++ } ++ ++ public String getJid() ++ { ++ return jid; ++ } ++ ++ public String getId() ++ { ++ return id; ++ } ++ ++ @Override ++ public String toXML() ++ { ++ StringBuilder builder = new StringBuilder("<"); ++ builder.append(getElementName()); ++ XmlUtils.appendAttribute(builder, "jid", jid); ++ ++ if (getNode() != null) ++ XmlUtils.appendAttribute(builder, "node", getNode()); ++ ++ if (id != null) ++ XmlUtils.appendAttribute(builder, "subid", id); ++ ++ builder.append("/>"); ++ return builder.toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/listener/ItemDeleteListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/listener/ItemDeleteListener.java +new file mode 100644 +index 0000000..d228e8f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/listener/ItemDeleteListener.java +@@ -0,0 +1,41 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.listener; ++ ++import org.jivesoftware.smackx.pubsub.ItemDeleteEvent; ++import org.jivesoftware.smackx.pubsub.LeafNode; ++ ++/** ++ * Defines the listener for item deletion events from a node. ++ * ++ * @see LeafNode#addItemDeleteListener(ItemDeleteListener) ++ * ++ * @author Robin Collier ++ */ ++public interface ItemDeleteListener ++{ ++ /** ++ * Called when items are deleted from a node the listener is ++ * registered with. ++ * ++ * @param items The event with item deletion details ++ */ ++ void handleDeletedItems(ItemDeleteEvent items); ++ ++ /** ++ * Called when all items are deleted from a node the listener is ++ * registered with. ++ */ ++ void handlePurge(); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/listener/ItemEventListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/listener/ItemEventListener.java +new file mode 100644 +index 0000000..714b2c0 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/listener/ItemEventListener.java +@@ -0,0 +1,36 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.listener; ++ ++import org.jivesoftware.smackx.pubsub.Item; ++import org.jivesoftware.smackx.pubsub.ItemPublishEvent; ++import org.jivesoftware.smackx.pubsub.LeafNode; ++ ++/** ++ * Defines the listener for items being published to a node. ++ * ++ * @see LeafNode#addItemEventListener(ItemEventListener) ++ * ++ * @author Robin Collier ++ */ ++public interface ItemEventListener ++{ ++ /** ++ * Called whenever an item is published to the node the listener ++ * is registered with. ++ * ++ * @param items The publishing details. ++ */ ++ void handlePublishedItems(ItemPublishEvent items); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/listener/NodeConfigListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/listener/NodeConfigListener.java +new file mode 100644 +index 0000000..39db5a5 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/listener/NodeConfigListener.java +@@ -0,0 +1,35 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.listener; ++ ++import org.jivesoftware.smackx.pubsub.ConfigurationEvent; ++import org.jivesoftware.smackx.pubsub.LeafNode; ++ ++/** ++ * Defines the listener for a node being configured. ++ * ++ * @see LeafNode#addConfigurationListener(NodeConfigListener) ++ * ++ * @author Robin Collier ++ */ ++public interface NodeConfigListener ++{ ++ /** ++ * Called whenever the node the listener ++ * is registered with is configured. ++ * ++ * @param config The configuration details. ++ */ ++ void handleNodeConfiguration(ConfigurationEvent config); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/packet/PubSub.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/packet/PubSub.java +new file mode 100644 +index 0000000..5aa4865 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/packet/PubSub.java +@@ -0,0 +1,106 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.pubsub.PubSubElementType; ++ ++/** ++ * The standard PubSub extension of an {@link IQ} packet. This is the topmost ++ * element of all pubsub requests and replies as defined in the Publish-Subscribe ++ * specification. ++ * ++ * @author Robin Collier ++ */ ++public class PubSub extends IQ ++{ ++ private PubSubNamespace ns = PubSubNamespace.BASIC; ++ ++ /** ++ * Returns the XML element name of the extension sub-packet root element. ++ * ++ * @return the XML element name of the packet extension. ++ */ ++ public String getElementName() { ++ return "pubsub"; ++ } ++ ++ /** ++ * Returns the XML namespace of the extension sub-packet root element. ++ * According the specification the namespace is ++ * http://jabber.org/protocol/pubsub with a specific fragment depending ++ * on the request. The namespace is defined at XMPP Registrar at ++ * ++ * The default value has no fragment. ++ * ++ * @return the XML namespace of the packet extension. ++ */ ++ public String getNamespace() ++ { ++ return ns.getXmlns(); ++ } ++ ++ /** ++ * Set the namespace for the packet if it something other than the default ++ * case of {@link PubSubNamespace#BASIC}. The {@link #getNamespace()} method will return ++ * the result of calling {@link PubSubNamespace#getXmlns()} on the specified enum. ++ * ++ * @param ns - The new value for the namespace. ++ */ ++ public void setPubSubNamespace(PubSubNamespace ns) ++ { ++ this.ns = ns; ++ } ++ ++ public PacketExtension getExtension(PubSubElementType elem) ++ { ++ return getExtension(elem.getElementName(), elem.getNamespace().getXmlns()); ++ } ++ ++ /** ++ * Returns the current value of the namespace. The {@link #getNamespace()} method will return ++ * the result of calling {@link PubSubNamespace#getXmlns()} this value. ++ * ++ * @return The current value of the namespace. ++ */ ++ public PubSubNamespace getPubSubNamespace() ++ { ++ return ns; ++ } ++ /** ++ * Returns the XML representation of a pubsub element according the specification. ++ * ++ * The XML representation will be inside of an iq packet like ++ * in the following example: ++ *
      ++     * <iq type='set' id="MlIpV-4" to="pubsub.gato.home" from="gato3@gato.home/Smack">
      ++     *     <pubsub xmlns="http://jabber.org/protocol/pubsub">
      ++     *                      :
      ++     *         Specific request extension
      ++     *                      :
      ++     *     </pubsub>
      ++     * </iq>
      ++     * 
      ++ * ++ */ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append("\">"); ++ buf.append(getExtensionsXML()); ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/packet/PubSubNamespace.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/packet/PubSubNamespace.java +new file mode 100644 +index 0000000..eecf959 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/packet/PubSubNamespace.java +@@ -0,0 +1,63 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.packet; ++ ++/** ++ * Defines all the valid namespaces that are used with the {@link PubSub} packet ++ * as defined by the specification. ++ * ++ * @author Robin Collier ++ */ ++public enum PubSubNamespace ++{ ++ BASIC(null), ++ ERROR("errors"), ++ EVENT("event"), ++ OWNER("owner"); ++ ++ private String fragment; ++ ++ private PubSubNamespace(String fragment) ++ { ++ this.fragment = fragment; ++ } ++ ++ public String getXmlns() ++ { ++ String ns = "http://jabber.org/protocol/pubsub"; ++ ++ if (fragment != null) ++ ns += '#' + fragment; ++ ++ return ns; ++ } ++ ++ public String getFragment() ++ { ++ return fragment; ++ } ++ ++ public static PubSubNamespace valueOfFromXmlns(String ns) ++ { ++ int index = ns.lastIndexOf('#'); ++ ++ if (index != -1) ++ { ++ String suffix = ns.substring(ns.lastIndexOf('#')+1); ++ return valueOf(suffix.toUpperCase()); ++ } ++ else ++ return BASIC; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/packet/SyncPacketSend.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/packet/SyncPacketSend.java +new file mode 100644 +index 0000000..080129b +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/packet/SyncPacketSend.java +@@ -0,0 +1,63 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.packet; ++ ++import org.jivesoftware.smack.PacketCollector; ++import org.jivesoftware.smack.SmackConfiguration; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.packet.Packet; ++ ++/** ++ * Utility class for doing synchronous calls to the server. Provides several ++ * methods for sending a packet to the server and waiting for the reply. ++ * ++ * @author Robin Collier ++ */ ++final public class SyncPacketSend ++{ ++ private SyncPacketSend() ++ { } ++ ++ static public Packet getReply(Connection connection, Packet packet, long timeout) ++ throws XMPPException ++ { ++ PacketFilter responseFilter = new PacketIDFilter(packet.getPacketID()); ++ PacketCollector response = connection.createPacketCollector(responseFilter); ++ ++ connection.sendPacket(packet); ++ ++ // Wait up to a certain number of seconds for a reply. ++ Packet result = response.nextResult(timeout); ++ ++ // Stop queuing results ++ response.cancel(); ++ ++ if (result == null) { ++ throw new XMPPException("No response from server."); ++ } ++ else if (result.getError() != null) { ++ throw new XMPPException(result.getError()); ++ } ++ return result; ++ } ++ ++ static public Packet getReply(Connection connection, Packet packet) ++ throws XMPPException ++ { ++ return getReply(connection, packet, SmackConfiguration.getPacketReplyTimeout()); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/AffiliationProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/AffiliationProvider.java +new file mode 100644 +index 0000000..892eec6 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/AffiliationProvider.java +@@ -0,0 +1,37 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.provider; ++ ++import java.util.List; ++import java.util.Map; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; ++import org.jivesoftware.smackx.pubsub.Affiliation; ++ ++/** ++ * Parses the affiliation element out of the reply stanza from the server ++ * as specified in the affiliation schema. ++ * ++ * @author Robin Collier ++ */ ++public class AffiliationProvider extends EmbeddedExtensionProvider ++{ ++ @Override ++ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) ++ { ++ return new Affiliation(attributeMap.get("jid"), attributeMap.get("node"), Affiliation.Type.valueOf(attributeMap.get("affiliation"))); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/AffiliationsProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/AffiliationsProvider.java +new file mode 100644 +index 0000000..ee7af05 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/AffiliationsProvider.java +@@ -0,0 +1,38 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.provider; ++ ++import java.util.List; ++import java.util.Map; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; ++import org.jivesoftware.smackx.pubsub.Affiliation; ++import org.jivesoftware.smackx.pubsub.AffiliationsExtension; ++ ++/** ++ * Parses the affiliations element out of the reply stanza from the server ++ * as specified in the affiliation schema. ++ * ++ * @author Robin Collier ++ */public class AffiliationsProvider extends EmbeddedExtensionProvider ++{ ++ @Override ++ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) ++ { ++ return new AffiliationsExtension(attributeMap.get("node"), (List)content); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/ConfigEventProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/ConfigEventProvider.java +new file mode 100644 +index 0000000..30e3017 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/ConfigEventProvider.java +@@ -0,0 +1,42 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.provider; ++ ++import java.util.List; ++import java.util.Map; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.packet.DataForm; ++import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; ++import org.jivesoftware.smackx.pubsub.ConfigurationEvent; ++import org.jivesoftware.smackx.pubsub.ConfigureForm; ++ ++/** ++ * Parses the node configuration element out of the message event stanza from ++ * the server as specified in the configuration schema. ++ * ++ * @author Robin Collier ++ */ ++public class ConfigEventProvider extends EmbeddedExtensionProvider ++{ ++ @Override ++ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attMap, List content) ++ { ++ if (content.size() == 0) ++ return new ConfigurationEvent(attMap.get("node")); ++ else ++ return new ConfigurationEvent(attMap.get("node"), new ConfigureForm((DataForm)content.iterator().next())); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/EventProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/EventProvider.java +new file mode 100644 +index 0000000..ef5671e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/EventProvider.java +@@ -0,0 +1,38 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.provider; ++ ++import java.util.List; ++import java.util.Map; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; ++import org.jivesoftware.smackx.pubsub.EventElement; ++import org.jivesoftware.smackx.pubsub.EventElementType; ++import org.jivesoftware.smackx.pubsub.NodeExtension; ++ ++/** ++ * Parses the event element out of the message stanza from ++ * the server as specified in the event schema. ++ * ++ * @author Robin Collier ++ */ ++public class EventProvider extends EmbeddedExtensionProvider ++{ ++ @Override ++ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attMap, List content) ++ { ++ return new EventElement(EventElementType.valueOf(content.get(0).getElementName()), (NodeExtension)content.get(0)); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/FormNodeProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/FormNodeProvider.java +new file mode 100644 +index 0000000..da75b24 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/FormNodeProvider.java +@@ -0,0 +1,39 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.provider; ++ ++import java.util.List; ++import java.util.Map; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.packet.DataForm; ++import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; ++import org.jivesoftware.smackx.pubsub.FormNode; ++import org.jivesoftware.smackx.pubsub.FormNodeType; ++ ++/** ++ * Parses one of several elements used in pubsub that contain a form of some kind as a child element. The ++ * elements and namespaces supported is defined in {@link FormNodeType}. ++ * ++ * @author Robin Collier ++ */ ++public class FormNodeProvider extends EmbeddedExtensionProvider ++{ ++ @Override ++ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) ++ { ++ return new FormNode(FormNodeType.valueOfFromElementName(currentElement, currentNamespace), attributeMap.get("node"), new Form((DataForm)content.iterator().next())); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java +new file mode 100644 +index 0000000..aabd4cb +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java +@@ -0,0 +1,76 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.provider; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.jivesoftware.smack.provider.ProviderManager; ++import org.jivesoftware.smack.util.PacketParserUtils; ++import org.jivesoftware.smackx.pubsub.Item; ++import org.jivesoftware.smackx.pubsub.PayloadItem; ++import org.jivesoftware.smackx.pubsub.SimplePayload; ++import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * Parses an item element as is defined in both the {@link PubSubNamespace#BASIC} and {@link PubSubNamespace#EVENT} ++ * namespaces. To parse the item contents, it will use whatever {@link PacketExtensionProvider} is registered in ++ * smack.providers for its element name and namespace. If no provider is registered, it will return a {@link SimplePayload}. ++ * ++ * @author Robin Collier ++ */ ++public class ItemProvider implements PacketExtensionProvider ++{ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception ++ { ++ String id = parser.getAttributeValue(null, "id"); ++ String node = parser.getAttributeValue(null, "node"); ++ String elem = parser.getName(); ++ ++ int tag = parser.next(); ++ ++ if (tag == XmlPullParser.END_TAG) ++ { ++ return new Item(id, node); ++ } ++ else ++ { ++ String payloadElemName = parser.getName(); ++ String payloadNS = parser.getNamespace(); ++ ++ if (ProviderManager.getInstance().getExtensionProvider(payloadElemName, payloadNS) == null) ++ { ++ boolean done = false; ++ StringBuilder payloadText = new StringBuilder(); ++ ++ while (!done) ++ { ++ if (tag == XmlPullParser.END_TAG && parser.getName().equals(elem)) ++ done = true; ++ else if (!((tag == XmlPullParser.START_TAG) && parser.isEmptyElementTag())) ++ payloadText.append(parser.getText()); ++ ++ if (!done) ++ tag = parser.next(); ++ } ++ return new PayloadItem(id, node, new SimplePayload(payloadElemName, payloadNS, payloadText.toString())); ++ } ++ else ++ { ++ return new PayloadItem(id, node, PacketParserUtils.parsePacketExtension(payloadElemName, payloadNS, parser)); ++ } ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/ItemsProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/ItemsProvider.java +new file mode 100644 +index 0000000..01cb9d4 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/ItemsProvider.java +@@ -0,0 +1,38 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.provider; ++ ++import java.util.List; ++import java.util.Map; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; ++import org.jivesoftware.smackx.pubsub.ItemsExtension; ++ ++/** ++ * Parses the items element out of the message event stanza from ++ * the server as specified in the items schema. ++ * ++ * @author Robin Collier ++ */ ++public class ItemsProvider extends EmbeddedExtensionProvider ++{ ++ ++ @Override ++ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) ++ { ++ return new ItemsExtension(ItemsExtension.ItemsElementType.items, attributeMap.get("node"), content); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/PubSubProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/PubSubProvider.java +new file mode 100644 +index 0000000..742f219 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/PubSubProvider.java +@@ -0,0 +1,62 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.provider; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smack.util.PacketParserUtils; ++import org.jivesoftware.smackx.pubsub.packet.PubSub; ++import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * Parses the root pubsub packet extensions of the {@link IQ} packet and returns ++ * a {@link PubSub} instance. ++ * ++ * @author Robin Collier ++ */ ++public class PubSubProvider implements IQProvider ++{ ++ public IQ parseIQ(XmlPullParser parser) throws Exception ++ { ++ PubSub pubsub = new PubSub(); ++ String namespace = parser.getNamespace(); ++ pubsub.setPubSubNamespace(PubSubNamespace.valueOfFromXmlns(namespace)); ++ boolean done = false; ++ ++ while (!done) ++ { ++ int eventType = parser.next(); ++ ++ if (eventType == XmlPullParser.START_TAG) ++ { ++ PacketExtension ext = PacketParserUtils.parsePacketExtension(parser.getName(), namespace, parser); ++ ++ if (ext != null) ++ { ++ pubsub.addExtension(ext); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) ++ { ++ if (parser.getName().equals("pubsub")) ++ { ++ done = true; ++ } ++ } ++ } ++ return pubsub; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/RetractEventProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/RetractEventProvider.java +new file mode 100644 +index 0000000..8fa3337 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/RetractEventProvider.java +@@ -0,0 +1,38 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.provider; ++ ++import java.util.List; ++import java.util.Map; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; ++import org.jivesoftware.smackx.pubsub.RetractItem; ++ ++/** ++ * Parses the retract element out of the message event stanza from ++ * the server as specified in the retract schema. ++ * This element is a child of the items element. ++ * ++ * @author Robin Collier ++ */ ++public class RetractEventProvider extends EmbeddedExtensionProvider ++{ ++ @Override ++ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) ++ { ++ return new RetractItem(attributeMap.get("id")); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/SimpleNodeProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/SimpleNodeProvider.java +new file mode 100644 +index 0000000..d2b7d30 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/SimpleNodeProvider.java +@@ -0,0 +1,37 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.provider; ++ ++import java.util.List; ++import java.util.Map; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; ++import org.jivesoftware.smackx.pubsub.NodeExtension; ++import org.jivesoftware.smackx.pubsub.PubSubElementType; ++ ++/** ++ * Parses simple elements that only contain a node attribute. This is common amongst many of the ++ * elements defined in the pubsub specification. For this common case a {@link NodeExtension} is returned. ++ * ++ * @author Robin Collier ++ */ ++public class SimpleNodeProvider extends EmbeddedExtensionProvider ++{ ++ @Override ++ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) ++ { ++ return new NodeExtension(PubSubElementType.valueOfFromElemName(currentElement, currentNamespace), attributeMap.get("node")); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/SubscriptionProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/SubscriptionProvider.java +new file mode 100644 +index 0000000..eccbe08 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/SubscriptionProvider.java +@@ -0,0 +1,52 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.provider; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.jivesoftware.smackx.pubsub.Subscription; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * Parses the subscription element out of the pubsub IQ message from ++ * the server as specified in the subscription schema. ++ * ++ * @author Robin Collier ++ */ ++public class SubscriptionProvider implements PacketExtensionProvider ++{ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception ++ { ++ String jid = parser.getAttributeValue(null, "jid"); ++ String nodeId = parser.getAttributeValue(null, "node"); ++ String subId = parser.getAttributeValue(null, "subid"); ++ String state = parser.getAttributeValue(null, "subscription"); ++ boolean isRequired = false; ++ ++ int tag = parser.next(); ++ ++ if ((tag == XmlPullParser.START_TAG) && parser.getName().equals("subscribe-options")) ++ { ++ tag = parser.next(); ++ ++ if ((tag == XmlPullParser.START_TAG) && parser.getName().equals("required")) ++ isRequired = true; ++ ++ while (parser.next() != XmlPullParser.END_TAG && parser.getName() != "subscribe-options"); ++ } ++ while (parser.getEventType() != XmlPullParser.END_TAG) parser.next(); ++ return new Subscription(jid, nodeId, subId, (state == null ? null : Subscription.State.valueOf(state)), isRequired); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/SubscriptionsProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/SubscriptionsProvider.java +new file mode 100644 +index 0000000..94dc61d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/SubscriptionsProvider.java +@@ -0,0 +1,38 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.provider; ++ ++import java.util.List; ++import java.util.Map; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; ++import org.jivesoftware.smackx.pubsub.Subscription; ++import org.jivesoftware.smackx.pubsub.SubscriptionsExtension; ++ ++/** ++ * Parses the subscriptions element out of the pubsub IQ message from ++ * the server as specified in the subscriptions schema. ++ * ++ * @author Robin Collier ++ */ ++public class SubscriptionsProvider extends EmbeddedExtensionProvider ++{ ++ @Override ++ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) ++ { ++ return new SubscriptionsExtension(attributeMap.get("node"), (List)content); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/util/NodeUtils.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/util/NodeUtils.java +new file mode 100644 +index 0000000..414601f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/util/NodeUtils.java +@@ -0,0 +1,43 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.util; ++ ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.pubsub.ConfigureForm; ++import org.jivesoftware.smackx.pubsub.FormNode; ++import org.jivesoftware.smackx.pubsub.PubSubElementType; ++ ++/** ++ * Utility for extracting information from packets. ++ * ++ * @author Robin Collier ++ */ ++public class NodeUtils ++{ ++ /** ++ * Get a {@link ConfigureForm} from a packet. ++ * ++ * @param packet ++ * @param elem ++ * @return The configuration form ++ */ ++ public static ConfigureForm getFormFromPacket(Packet packet, PubSubElementType elem) ++ { ++ FormNode config = (FormNode)packet.getExtension(elem.getElementName(), elem.getNamespace().getXmlns()); ++ Form formReply = config.getForm(); ++ return new ConfigureForm(formReply); ++ ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/util/XmlUtils.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/util/XmlUtils.java +new file mode 100644 +index 0000000..95ffca5 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/util/XmlUtils.java +@@ -0,0 +1,41 @@ ++/** ++ * All rights reserved. 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.smackx.pubsub.util; ++ ++import java.io.StringReader; ++ ++import javax.xml.transform.OutputKeys; ++import javax.xml.transform.Transformer; ++import javax.xml.transform.TransformerFactory; ++import javax.xml.transform.stream.StreamResult; ++import javax.xml.transform.stream.StreamSource; ++ ++/** ++ * Simple utility for pretty printing xml. ++ * ++ * @author Robin Collier ++ */ ++public class XmlUtils ++{ ++ ++ static public void appendAttribute(StringBuilder builder, String att, String value) ++ { ++ builder.append(" "); ++ builder.append(att); ++ builder.append("='"); ++ builder.append(value); ++ builder.append("'"); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/search/SimpleUserSearch.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/search/SimpleUserSearch.java +new file mode 100644 +index 0000000..3d5f646 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/search/SimpleUserSearch.java +@@ -0,0 +1,151 @@ ++/** ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.search; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.FormField; ++import org.jivesoftware.smackx.ReportedData; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++ ++/** ++ * SimpleUserSearch is used to support the non-dataform type of JEP 55. This provides ++ * the mechanism for allowing always type ReportedData to be returned by any search result, ++ * regardless of the form of the data returned from the server. ++ * ++ * @author Derek DeMoro ++ */ ++class SimpleUserSearch extends IQ { ++ ++ private Form form; ++ private ReportedData data; ++ ++ public void setForm(Form form) { ++ this.form = form; ++ } ++ ++ public ReportedData getReportedData() { ++ return data; ++ } ++ ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ buf.append(getItemsToSearch()); ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ private String getItemsToSearch() { ++ StringBuilder buf = new StringBuilder(); ++ ++ if (form == null) { ++ form = Form.getFormFrom(this); ++ } ++ ++ if (form == null) { ++ return ""; ++ } ++ ++ Iterator fields = form.getFields(); ++ while (fields.hasNext()) { ++ FormField field = fields.next(); ++ String name = field.getVariable(); ++ String value = getSingleValue(field); ++ if (value.trim().length() > 0) { ++ buf.append("<").append(name).append(">").append(value).append(""); ++ } ++ } ++ ++ return buf.toString(); ++ } ++ ++ private static String getSingleValue(FormField formField) { ++ Iterator values = formField.getValues(); ++ while (values.hasNext()) { ++ return values.next(); ++ } ++ return ""; ++ } ++ ++ protected void parseItems(XmlPullParser parser) throws Exception { ++ ReportedData data = new ReportedData(); ++ data.addColumn(new ReportedData.Column("JID", "jid", "text-single")); ++ ++ boolean done = false; ++ ++ List fields = new ArrayList(); ++ while (!done) { ++ if (parser.getAttributeCount() > 0) { ++ String jid = parser.getAttributeValue("", "jid"); ++ List valueList = new ArrayList(); ++ valueList.add(jid); ++ ReportedData.Field field = new ReportedData.Field("jid", valueList); ++ fields.add(field); ++ } ++ ++ int eventType = parser.next(); ++ ++ if (eventType == XmlPullParser.START_TAG && parser.getName().equals("item")) { ++ fields = new ArrayList(); ++ } ++ else if (eventType == XmlPullParser.END_TAG && parser.getName().equals("item")) { ++ ReportedData.Row row = new ReportedData.Row(fields); ++ data.addRow(row); ++ } ++ else if (eventType == XmlPullParser.START_TAG) { ++ String name = parser.getName(); ++ String value = parser.nextText(); ++ ++ List valueList = new ArrayList(); ++ valueList.add(value); ++ ReportedData.Field field = new ReportedData.Field(name, valueList); ++ fields.add(field); ++ ++ boolean exists = false; ++ Iterator cols = data.getColumns(); ++ while (cols.hasNext()) { ++ ReportedData.Column column = (ReportedData.Column) cols.next(); ++ if (column.getVariable().equals(name)) { ++ exists = true; ++ } ++ } ++ ++ // Column name should be the same ++ if (!exists) { ++ ReportedData.Column column = new ReportedData.Column(name, name, "text-single"); ++ data.addColumn(column); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("query")) { ++ done = true; ++ } ++ } ++ ++ } ++ ++ this.data = data; ++ } ++ ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/search/UserSearch.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/search/UserSearch.java +new file mode 100644 +index 0000000..781dd9a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/search/UserSearch.java +@@ -0,0 +1,255 @@ ++/** ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.search; ++ ++import org.jivesoftware.smack.PacketCollector; ++import org.jivesoftware.smack.SmackConfiguration; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smack.util.PacketParserUtils; ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.FormField; ++import org.jivesoftware.smackx.ReportedData; ++import org.jivesoftware.smackx.packet.DataForm; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * Implements the protocol currently used to search information repositories on the Jabber network. To date, the jabber:iq:search protocol ++ * has been used mainly to search for people who have registered with user directories (e.g., the "Jabber User Directory" hosted at users.jabber.org). ++ * However, the jabber:iq:search protocol is not limited to user directories, and could be used to search other Jabber information repositories ++ * (such as chatroom directories) or even to provide a Jabber interface to conventional search engines. ++ *

      ++ * The basic functionality is to query an information repository regarding the possible search fields, to send a search query, and to receive search results. ++ * ++ * @author Derek DeMoro ++ */ ++public class UserSearch extends IQ { ++ ++ /** ++ * Creates a new instance of UserSearch. ++ */ ++ public UserSearch() { ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ buf.append(getExtensionsXML()); ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ /** ++ * Returns the form for all search fields supported by the search service. ++ * ++ * @param con the current Connection. ++ * @param searchService the search service to use. (ex. search.jivesoftware.com) ++ * @return the search form received by the server. ++ * @throws org.jivesoftware.smack.XMPPException ++ * thrown if a server error has occurred. ++ */ ++ public Form getSearchForm(Connection con, String searchService) throws XMPPException { ++ UserSearch search = new UserSearch(); ++ search.setType(IQ.Type.GET); ++ search.setTo(searchService); ++ ++ PacketCollector collector = con.createPacketCollector(new PacketIDFilter(search.getPacketID())); ++ con.sendPacket(search); ++ ++ IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return Form.getFormFrom(response); ++ } ++ ++ /** ++ * Sends the filled out answer form to be sent and queried by the search service. ++ * ++ * @param con the current Connection. ++ * @param searchForm the Form to send for querying. ++ * @param searchService the search service to use. (ex. search.jivesoftware.com) ++ * @return ReportedData the data found from the query. ++ * @throws org.jivesoftware.smack.XMPPException ++ * thrown if a server error has occurred. ++ */ ++ public ReportedData sendSearchForm(Connection con, Form searchForm, String searchService) throws XMPPException { ++ UserSearch search = new UserSearch(); ++ search.setType(IQ.Type.SET); ++ search.setTo(searchService); ++ search.addExtension(searchForm.getDataFormToSend()); ++ ++ PacketCollector collector = con.createPacketCollector(new PacketIDFilter(search.getPacketID())); ++ ++ con.sendPacket(search); ++ ++ IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ return sendSimpleSearchForm(con, searchForm, searchService); ++ } ++ ++ ++ return ReportedData.getReportedDataFrom(response); ++ } ++ ++ /** ++ * Sends the filled out answer form to be sent and queried by the search service. ++ * ++ * @param con the current Connection. ++ * @param searchForm the Form to send for querying. ++ * @param searchService the search service to use. (ex. search.jivesoftware.com) ++ * @return ReportedData the data found from the query. ++ * @throws org.jivesoftware.smack.XMPPException ++ * thrown if a server error has occurred. ++ */ ++ public ReportedData sendSimpleSearchForm(Connection con, Form searchForm, String searchService) throws XMPPException { ++ SimpleUserSearch search = new SimpleUserSearch(); ++ search.setForm(searchForm); ++ search.setType(IQ.Type.SET); ++ search.setTo(searchService); ++ ++ PacketCollector collector = con.createPacketCollector(new PacketIDFilter(search.getPacketID())); ++ ++ con.sendPacket(search); ++ ++ IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ ++ if (response instanceof SimpleUserSearch) { ++ return ((SimpleUserSearch) response).getReportedData(); ++ } ++ return null; ++ } ++ ++ /** ++ * Internal Search service Provider. ++ */ ++ public static class Provider implements IQProvider { ++ ++ /** ++ * Provider Constructor. ++ */ ++ public Provider() { ++ super(); ++ } ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ UserSearch search = null; ++ SimpleUserSearch simpleUserSearch = new SimpleUserSearch(); ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG && parser.getName().equals("instructions")) { ++ buildDataForm(simpleUserSearch, parser.nextText(), parser); ++ return simpleUserSearch; ++ } ++ else if (eventType == XmlPullParser.START_TAG && parser.getName().equals("item")) { ++ simpleUserSearch.parseItems(parser); ++ return simpleUserSearch; ++ } ++ else if (eventType == XmlPullParser.START_TAG && parser.getNamespace().equals("jabber:x:data")) { ++ // Otherwise, it must be a packet extension. ++ search = new UserSearch(); ++ search.addExtension(PacketParserUtils.parsePacketExtension(parser.getName(), ++ parser.getNamespace(), parser)); ++ ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("query")) { ++ done = true; ++ } ++ } ++ } ++ ++ if (search != null) { ++ return search; ++ } ++ return simpleUserSearch; ++ } ++ } ++ ++ private static void buildDataForm(SimpleUserSearch search, String instructions, XmlPullParser parser) throws Exception { ++ DataForm dataForm = new DataForm(Form.TYPE_FORM); ++ boolean done = false; ++ dataForm.setTitle("User Search"); ++ dataForm.addInstruction(instructions); ++ while (!done) { ++ int eventType = parser.next(); ++ ++ if (eventType == XmlPullParser.START_TAG && !parser.getNamespace().equals("jabber:x:data")) { ++ String name = parser.getName(); ++ FormField field = new FormField(name); ++ ++ // Handle hard coded values. ++ if(name.equals("first")){ ++ field.setLabel("First Name"); ++ } ++ else if(name.equals("last")){ ++ field.setLabel("Last Name"); ++ } ++ else if(name.equals("email")){ ++ field.setLabel("Email Address"); ++ } ++ else if(name.equals("nick")){ ++ field.setLabel("Nickname"); ++ } ++ ++ field.setType(FormField.TYPE_TEXT_SINGLE); ++ dataForm.addField(field); ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("query")) { ++ done = true; ++ } ++ } ++ else if (eventType == XmlPullParser.START_TAG && parser.getNamespace().equals("jabber:x:data")) { ++ search.addExtension(PacketParserUtils.parsePacketExtension(parser.getName(), ++ parser.getNamespace(), parser)); ++ done = true; ++ } ++ } ++ if (search.getExtension("x", "jabber:x:data") == null) { ++ search.addExtension(dataForm); ++ } ++ } ++ ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/search/UserSearchManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/search/UserSearchManager.java +new file mode 100644 +index 0000000..83c19e3 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/search/UserSearchManager.java +@@ -0,0 +1,124 @@ ++/** ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.search; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.ReportedData; ++import org.jivesoftware.smackx.ServiceDiscoveryManager; ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++import org.jivesoftware.smackx.packet.DiscoverItems; ++ ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.Iterator; ++import java.util.List; ++ ++/** ++ * The UserSearchManager is a facade built upon Jabber Search Services (JEP-055) to allow for searching ++ * repositories on a Jabber Server. This implementation allows for transparency of implementation of ++ * searching (DataForms or No DataForms), but allows the user to simply use the DataForm model for both ++ * types of support. ++ *

      ++ * Connection con = new XMPPConnection("jabber.org");
      ++ * con.login("john", "doe");
      ++ * UserSearchManager search = new UserSearchManager(con, "users.jabber.org");
      ++ * Form searchForm = search.getSearchForm();
      ++ * Form answerForm = searchForm.createAnswerForm();
      ++ * answerForm.setAnswer("last", "DeMoro");
      ++ * ReportedData data = search.getSearchResults(answerForm);
      ++ * // Use Returned Data
      ++ * 
      ++ * ++ * @author Derek DeMoro ++ */ ++public class UserSearchManager { ++ ++ private Connection con; ++ private UserSearch userSearch; ++ ++ /** ++ * Creates a new UserSearchManager. ++ * ++ * @param con the Connection to use. ++ */ ++ public UserSearchManager(Connection con) { ++ this.con = con; ++ userSearch = new UserSearch(); ++ } ++ ++ /** ++ * Returns the form to fill out to perform a search. ++ * ++ * @param searchService the search service to query. ++ * @return the form to fill out to perform a search. ++ * @throws XMPPException thrown if a server error has occurred. ++ */ ++ public Form getSearchForm(String searchService) throws XMPPException { ++ return userSearch.getSearchForm(con, searchService); ++ } ++ ++ /** ++ * Submits a search form to the server and returns the resulting information ++ * in the form of ReportedData ++ * ++ * @param searchForm the Form to submit for searching. ++ * @param searchService the name of the search service to use. ++ * @return the ReportedData returned by the server. ++ * @throws XMPPException thrown if a server error has occurred. ++ */ ++ public ReportedData getSearchResults(Form searchForm, String searchService) throws XMPPException { ++ return userSearch.sendSearchForm(con, searchForm, searchService); ++ } ++ ++ ++ /** ++ * Returns a collection of search services found on the server. ++ * ++ * @return a Collection of search services found on the server. ++ * @throws XMPPException thrown if a server error has occurred. ++ */ ++ public Collection getSearchServices() throws XMPPException { ++ final List searchServices = new ArrayList(); ++ ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(con); ++ DiscoverItems items = discoManager.discoverItems(con.getServiceName()); ++ Iterator iter = items.getItems(); ++ while (iter.hasNext()) { ++ DiscoverItems.Item item = iter.next(); ++ try { ++ DiscoverInfo info; ++ try { ++ info = discoManager.discoverInfo(item.getEntityID()); ++ } ++ catch (XMPPException e) { ++ // Ignore Case ++ continue; ++ } ++ ++ if (info.containsFeature("jabber:iq:search")) { ++ searchServices.add(item.getEntityID()); ++ } ++ } ++ catch (Exception e) { ++ // No info found. ++ break; ++ } ++ } ++ return searchServices; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/MetaData.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/MetaData.java +new file mode 100644 +index 0000000..7673835 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/MetaData.java +@@ -0,0 +1,67 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup; ++ ++import java.util.Map; ++ ++import org.jivesoftware.smackx.workgroup.util.MetaDataUtils; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++ ++/** ++ * MetaData packet extension. ++ */ ++public class MetaData implements PacketExtension { ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "metadata"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; ++ ++ private Map metaData; ++ ++ public MetaData(Map metaData) { ++ this.metaData = metaData; ++ } ++ ++ /** ++ * @return the Map of metadata contained by this instance ++ */ ++ public Map getMetaData() { ++ return metaData; ++ } ++ ++ public String getElementName() { ++ return ELEMENT_NAME; ++ } ++ ++ public String getNamespace() { ++ return NAMESPACE; ++ } ++ ++ public String toXML() { ++ return MetaDataUtils.serializeMetaData(this.getMetaData()); ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/QueueUser.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/QueueUser.java +new file mode 100644 +index 0000000..89a1899 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/QueueUser.java +@@ -0,0 +1,85 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup; ++ ++import java.util.Date; ++ ++/** ++ * An immutable class which wraps up customer-in-queue data return from the server; depending on ++ * the type of information dispatched from the server, not all information will be available in ++ * any given instance. ++ * ++ * @author loki der quaeler ++ */ ++public class QueueUser { ++ ++ private String userID; ++ ++ private int queuePosition; ++ private int estimatedTime; ++ private Date joinDate; ++ ++ /** ++ * @param uid the user jid of the customer in the queue ++ * @param position the position customer sits in the queue ++ * @param time the estimate of how much longer the customer will be in the queue in seconds ++ * @param joinedAt the timestamp of when the customer entered the queue ++ */ ++ public QueueUser (String uid, int position, int time, Date joinedAt) { ++ super(); ++ ++ this.userID = uid; ++ this.queuePosition = position; ++ this.estimatedTime = time; ++ this.joinDate = joinedAt; ++ } ++ ++ /** ++ * @return the user jid of the customer in the queue ++ */ ++ public String getUserID () { ++ return this.userID; ++ } ++ ++ /** ++ * @return the position in the queue at which the customer sits, or -1 if the update which ++ * this instance embodies is only a time update instead ++ */ ++ public int getQueuePosition () { ++ return this.queuePosition; ++ } ++ ++ /** ++ * @return the estimated time remaining of the customer in the queue in seconds, or -1 if ++ * if the update which this instance embodies is only a position update instead ++ */ ++ public int getEstimatedRemainingTime () { ++ return this.estimatedTime; ++ } ++ ++ /** ++ * @return the timestamp of when this customer entered the queue, or null if the server did not ++ * provide this information ++ */ ++ public Date getQueueJoinTimestamp () { ++ return this.joinDate; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/WorkgroupInvitation.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/WorkgroupInvitation.java +new file mode 100644 +index 0000000..8409966 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/WorkgroupInvitation.java +@@ -0,0 +1,133 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup; ++ ++import java.util.Map; ++ ++/** ++ * An immutable class wrapping up the basic information which comprises a group chat invitation. ++ * ++ * @author loki der quaeler ++ */ ++public class WorkgroupInvitation { ++ ++ protected String uniqueID; ++ ++ protected String sessionID; ++ ++ protected String groupChatName; ++ protected String issuingWorkgroupName; ++ protected String messageBody; ++ protected String invitationSender; ++ protected Map metaData; ++ ++ /** ++ * This calls the 5-argument constructor with a null MetaData argument value ++ * ++ * @param jid the jid string with which the issuing AgentSession or Workgroup instance ++ * was created ++ * @param group the jid of the room to which the person is invited ++ * @param workgroup the jid of the workgroup issuing the invitation ++ * @param sessID the session id associated with the pending chat ++ * @param msgBody the body of the message which contained the invitation ++ * @param from the user jid who issued the invitation, if known, null otherwise ++ */ ++ public WorkgroupInvitation (String jid, String group, String workgroup, ++ String sessID, String msgBody, String from) { ++ this(jid, group, workgroup, sessID, msgBody, from, null); ++ } ++ ++ /** ++ * @param jid the jid string with which the issuing AgentSession or Workgroup instance ++ * was created ++ * @param group the jid of the room to which the person is invited ++ * @param workgroup the jid of the workgroup issuing the invitation ++ * @param sessID the session id associated with the pending chat ++ * @param msgBody the body of the message which contained the invitation ++ * @param from the user jid who issued the invitation, if known, null otherwise ++ * @param metaData the metadata sent with the invitation ++ */ ++ public WorkgroupInvitation (String jid, String group, String workgroup, String sessID, String msgBody, ++ String from, Map metaData) { ++ super(); ++ ++ this.uniqueID = jid; ++ this.sessionID = sessID; ++ this.groupChatName = group; ++ this.issuingWorkgroupName = workgroup; ++ this.messageBody = msgBody; ++ this.invitationSender = from; ++ this.metaData = metaData; ++ } ++ ++ /** ++ * @return the jid string with which the issuing AgentSession or Workgroup instance ++ * was created. ++ */ ++ public String getUniqueID () { ++ return this.uniqueID; ++ } ++ ++ /** ++ * @return the session id associated with the pending chat; working backwards temporally, ++ * this session id should match the session id to the corresponding offer request ++ * which resulted in this invitation. ++ */ ++ public String getSessionID () { ++ return this.sessionID; ++ } ++ ++ /** ++ * @return the jid of the room to which the person is invited. ++ */ ++ public String getGroupChatName () { ++ return this.groupChatName; ++ } ++ ++ /** ++ * @return the name of the workgroup from which the invitation was issued. ++ */ ++ public String getWorkgroupName () { ++ return this.issuingWorkgroupName; ++ } ++ ++ /** ++ * @return the contents of the body-block of the message that housed this invitation. ++ */ ++ public String getMessageBody () { ++ return this.messageBody; ++ } ++ ++ /** ++ * @return the user who issued the invitation, or null if it wasn't known. ++ */ ++ public String getInvitationSender () { ++ return this.invitationSender; ++ } ++ ++ /** ++ * @return the meta data associated with the invitation, or null if this instance was ++ * constructed with none ++ */ ++ public Map getMetaData () { ++ return this.metaData; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/WorkgroupInvitationListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/WorkgroupInvitationListener.java +new file mode 100644 +index 0000000..bc73242 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/WorkgroupInvitationListener.java +@@ -0,0 +1,39 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup; ++ ++/** ++ * An interface which all classes interested in hearing about group chat invitations should ++ * implement. ++ * ++ * @author loki der quaeler ++ */ ++public interface WorkgroupInvitationListener { ++ ++ /** ++ * The implementing class instance will be notified via this method when an invitation ++ * to join a group chat has been received from the server. ++ * ++ * @param invitation an Invitation instance embodying the information pertaining to the ++ * invitation ++ */ ++ public void invitationReceived(WorkgroupInvitation invitation); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/Agent.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/Agent.java +new file mode 100644 +index 0000000..bebac37 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/Agent.java +@@ -0,0 +1,138 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.agent; ++ ++import org.jivesoftware.smackx.workgroup.packet.AgentInfo; ++import org.jivesoftware.smackx.workgroup.packet.AgentWorkgroups; ++import org.jivesoftware.smack.PacketCollector; ++import org.jivesoftware.smack.SmackConfiguration; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.packet.IQ; ++ ++import java.util.Collection; ++ ++/** ++ * The Agent class is used to represent one agent in a Workgroup Queue. ++ * ++ * @author Derek DeMoro ++ */ ++public class Agent { ++ private Connection connection; ++ private String workgroupJID; ++ ++ public static Collection getWorkgroups(String serviceJID, String agentJID, Connection connection) throws XMPPException { ++ AgentWorkgroups request = new AgentWorkgroups(agentJID); ++ request.setTo(serviceJID); ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); ++ // Send the request ++ connection.sendPacket(request); ++ ++ AgentWorkgroups response = (AgentWorkgroups)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response.getWorkgroups(); ++ } ++ ++ /** ++ * Constructs an Agent. ++ */ ++ Agent(Connection connection, String workgroupJID) { ++ this.connection = connection; ++ this.workgroupJID = workgroupJID; ++ } ++ ++ /** ++ * Return the agents JID ++ * ++ * @return - the agents JID. ++ */ ++ public String getUser() { ++ return connection.getUser(); ++ } ++ ++ /** ++ * Return the agents name. ++ * ++ * @return - the agents name. ++ */ ++ public String getName() throws XMPPException { ++ AgentInfo agentInfo = new AgentInfo(); ++ agentInfo.setType(IQ.Type.GET); ++ agentInfo.setTo(workgroupJID); ++ agentInfo.setFrom(getUser()); ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(agentInfo.getPacketID())); ++ // Send the request ++ connection.sendPacket(agentInfo); ++ ++ AgentInfo response = (AgentInfo)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response.getName(); ++ } ++ ++ /** ++ * Changes the name of the agent in the server. The server may have this functionality ++ * disabled for all the agents or for this agent in particular. If the agent is not ++ * allowed to change his name then an exception will be thrown with a service_unavailable ++ * error code. ++ * ++ * @param newName the new name of the agent. ++ * @throws XMPPException if the agent is not allowed to change his name or no response was ++ * obtained from the server. ++ */ ++ public void setName(String newName) throws XMPPException { ++ AgentInfo agentInfo = new AgentInfo(); ++ agentInfo.setType(IQ.Type.SET); ++ agentInfo.setTo(workgroupJID); ++ agentInfo.setFrom(getUser()); ++ agentInfo.setName(newName); ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(agentInfo.getPacketID())); ++ // Send the request ++ connection.sendPacket(agentInfo); ++ ++ IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/AgentRoster.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/AgentRoster.java +new file mode 100644 +index 0000000..155a1c3 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/AgentRoster.java +@@ -0,0 +1,386 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.agent; ++ ++import org.jivesoftware.smackx.workgroup.packet.AgentStatus; ++import org.jivesoftware.smackx.workgroup.packet.AgentStatusRequest; ++import org.jivesoftware.smack.PacketListener; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.filter.PacketFilter; ++import org.jivesoftware.smack.filter.PacketTypeFilter; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.packet.Presence; ++import org.jivesoftware.smack.util.StringUtils; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++ ++/** ++ * Manges information about the agents in a workgroup and their presence. ++ * ++ * @author Matt Tucker ++ * @see AgentSession#getAgentRoster() ++ */ ++public class AgentRoster { ++ ++ private static final int EVENT_AGENT_ADDED = 0; ++ private static final int EVENT_AGENT_REMOVED = 1; ++ private static final int EVENT_PRESENCE_CHANGED = 2; ++ ++ private Connection connection; ++ private String workgroupJID; ++ private List entries; ++ private List listeners; ++ private Map> presenceMap; ++ // The roster is marked as initialized when at least a single roster packet ++ // has been recieved and processed. ++ boolean rosterInitialized = false; ++ ++ /** ++ * Constructs a new AgentRoster. ++ * ++ * @param connection an XMPP connection. ++ */ ++ AgentRoster(Connection connection, String workgroupJID) { ++ this.connection = connection; ++ this.workgroupJID = workgroupJID; ++ entries = new ArrayList(); ++ listeners = new ArrayList(); ++ presenceMap = new HashMap>(); ++ // Listen for any roster packets. ++ PacketFilter rosterFilter = new PacketTypeFilter(AgentStatusRequest.class); ++ connection.addPacketListener(new AgentStatusListener(), rosterFilter); ++ // Listen for any presence packets. ++ connection.addPacketListener(new PresencePacketListener(), ++ new PacketTypeFilter(Presence.class)); ++ ++ // Send request for roster. ++ AgentStatusRequest request = new AgentStatusRequest(); ++ request.setTo(workgroupJID); ++ connection.sendPacket(request); ++ } ++ ++ /** ++ * Reloads the entire roster from the server. This is an asynchronous operation, ++ * which means the method will return immediately, and the roster will be ++ * reloaded at a later point when the server responds to the reload request. ++ */ ++ public void reload() { ++ AgentStatusRequest request = new AgentStatusRequest(); ++ request.setTo(workgroupJID); ++ connection.sendPacket(request); ++ } ++ ++ /** ++ * Adds a listener to this roster. The listener will be fired anytime one or more ++ * changes to the roster are pushed from the server. ++ * ++ * @param listener an agent roster listener. ++ */ ++ public void addListener(AgentRosterListener listener) { ++ synchronized (listeners) { ++ if (!listeners.contains(listener)) { ++ listeners.add(listener); ++ ++ // Fire events for the existing entries and presences in the roster ++ for (Iterator it = getAgents().iterator(); it.hasNext();) { ++ String jid = it.next(); ++ // Check again in case the agent is no longer in the roster (highly unlikely ++ // but possible) ++ if (entries.contains(jid)) { ++ // Fire the agent added event ++ listener.agentAdded(jid); ++ Map userPresences = presenceMap.get(jid); ++ if (userPresences != null) { ++ Iterator presences = userPresences.values().iterator(); ++ while (presences.hasNext()) { ++ // Fire the presence changed event ++ listener.presenceChanged(presences.next()); ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ /** ++ * Removes a listener from this roster. The listener will be fired anytime one or more ++ * changes to the roster are pushed from the server. ++ * ++ * @param listener a roster listener. ++ */ ++ public void removeListener(AgentRosterListener listener) { ++ synchronized (listeners) { ++ listeners.remove(listener); ++ } ++ } ++ ++ /** ++ * Returns a count of all agents in the workgroup. ++ * ++ * @return the number of agents in the workgroup. ++ */ ++ public int getAgentCount() { ++ return entries.size(); ++ } ++ ++ /** ++ * Returns all agents (String JID values) in the workgroup. ++ * ++ * @return all entries in the roster. ++ */ ++ public Set getAgents() { ++ Set agents = new HashSet(); ++ synchronized (entries) { ++ for (Iterator i = entries.iterator(); i.hasNext();) { ++ agents.add(i.next()); ++ } ++ } ++ return Collections.unmodifiableSet(agents); ++ } ++ ++ /** ++ * Returns true if the specified XMPP address is an agent in the workgroup. ++ * ++ * @param jid the XMPP address of the agent (eg "jsmith@example.com"). The ++ * address can be in any valid format (e.g. "domain/resource", "user@domain" ++ * or "user@domain/resource"). ++ * @return true if the XMPP address is an agent in the workgroup. ++ */ ++ public boolean contains(String jid) { ++ if (jid == null) { ++ return false; ++ } ++ synchronized (entries) { ++ for (Iterator i = entries.iterator(); i.hasNext();) { ++ String entry = i.next(); ++ if (entry.toLowerCase().equals(jid.toLowerCase())) { ++ return true; ++ } ++ } ++ } ++ return false; ++ } ++ ++ /** ++ * Returns the presence info for a particular agent, or null if the agent ++ * is unavailable (offline) or if no presence information is available.

      ++ * ++ * @param user a fully qualified xmpp JID. The address could be in any valid format (e.g. ++ * "domain/resource", "user@domain" or "user@domain/resource"). ++ * @return the agent's current presence, or null if the agent is unavailable ++ * or if no presence information is available.. ++ */ ++ public Presence getPresence(String user) { ++ String key = getPresenceMapKey(user); ++ Map userPresences = presenceMap.get(key); ++ if (userPresences == null) { ++ Presence presence = new Presence(Presence.Type.unavailable); ++ presence.setFrom(user); ++ return presence; ++ } ++ else { ++ // Find the resource with the highest priority ++ // Might be changed to use the resource with the highest availability instead. ++ Iterator it = userPresences.keySet().iterator(); ++ Presence p; ++ Presence presence = null; ++ ++ while (it.hasNext()) { ++ p = (Presence)userPresences.get(it.next()); ++ if (presence == null){ ++ presence = p; ++ } ++ else { ++ if (p.getPriority() > presence.getPriority()) { ++ presence = p; ++ } ++ } ++ } ++ if (presence == null) { ++ presence = new Presence(Presence.Type.unavailable); ++ presence.setFrom(user); ++ return presence; ++ } ++ else { ++ return presence; ++ } ++ } ++ } ++ ++ /** ++ * Returns the key to use in the presenceMap for a fully qualified xmpp ID. The roster ++ * can contain any valid address format such us "domain/resource", "user@domain" or ++ * "user@domain/resource". If the roster contains an entry associated with the fully qualified ++ * xmpp ID then use the fully qualified xmpp ID as the key in presenceMap, otherwise use the ++ * bare address. Note: When the key in presenceMap is a fully qualified xmpp ID, the ++ * userPresences is useless since it will always contain one entry for the user. ++ * ++ * @param user the fully qualified xmpp ID, e.g. jdoe@example.com/Work. ++ * @return the key to use in the presenceMap for the fully qualified xmpp ID. ++ */ ++ private String getPresenceMapKey(String user) { ++ String key = user; ++ if (!contains(user)) { ++ key = StringUtils.parseBareAddress(user).toLowerCase(); ++ } ++ return key; ++ } ++ ++ /** ++ * Fires event to listeners. ++ */ ++ private void fireEvent(int eventType, Object eventObject) { ++ AgentRosterListener[] listeners = null; ++ synchronized (this.listeners) { ++ listeners = new AgentRosterListener[this.listeners.size()]; ++ this.listeners.toArray(listeners); ++ } ++ for (int i = 0; i < listeners.length; i++) { ++ switch (eventType) { ++ case EVENT_AGENT_ADDED: ++ listeners[i].agentAdded((String)eventObject); ++ break; ++ case EVENT_AGENT_REMOVED: ++ listeners[i].agentRemoved((String)eventObject); ++ break; ++ case EVENT_PRESENCE_CHANGED: ++ listeners[i].presenceChanged((Presence)eventObject); ++ break; ++ } ++ } ++ } ++ ++ /** ++ * Listens for all presence packets and processes them. ++ */ ++ private class PresencePacketListener implements PacketListener { ++ public void processPacket(Packet packet) { ++ Presence presence = (Presence)packet; ++ String from = presence.getFrom(); ++ if (from == null) { ++ // TODO Check if we need to ignore these presences or this is a server bug? ++ System.out.println("Presence with no FROM: " + presence.toXML()); ++ return; ++ } ++ String key = getPresenceMapKey(from); ++ ++ // If an "available" packet, add it to the presence map. Each presence map will hold ++ // for a particular user a map with the presence packets saved for each resource. ++ if (presence.getType() == Presence.Type.available) { ++ // Ignore the presence packet unless it has an agent status extension. ++ AgentStatus agentStatus = (AgentStatus)presence.getExtension( ++ AgentStatus.ELEMENT_NAME, AgentStatus.NAMESPACE); ++ if (agentStatus == null) { ++ return; ++ } ++ // Ensure that this presence is coming from an Agent of the same workgroup ++ // of this Agent ++ else if (!workgroupJID.equals(agentStatus.getWorkgroupJID())) { ++ return; ++ } ++ Map userPresences; ++ // Get the user presence map ++ if (presenceMap.get(key) == null) { ++ userPresences = new HashMap(); ++ presenceMap.put(key, userPresences); ++ } ++ else { ++ userPresences = presenceMap.get(key); ++ } ++ // Add the new presence, using the resources as a key. ++ synchronized (userPresences) { ++ userPresences.put(StringUtils.parseResource(from), presence); ++ } ++ // Fire an event. ++ synchronized (entries) { ++ for (Iterator i = entries.iterator(); i.hasNext();) { ++ String entry = (String)i.next(); ++ if (entry.toLowerCase().equals(StringUtils.parseBareAddress(key).toLowerCase())) { ++ fireEvent(EVENT_PRESENCE_CHANGED, packet); ++ } ++ } ++ } ++ } ++ // If an "unavailable" packet, remove any entries in the presence map. ++ else if (presence.getType() == Presence.Type.unavailable) { ++ if (presenceMap.get(key) != null) { ++ Map userPresences = presenceMap.get(key); ++ synchronized (userPresences) { ++ userPresences.remove(StringUtils.parseResource(from)); ++ } ++ if (userPresences.isEmpty()) { ++ presenceMap.remove(key); ++ } ++ } ++ // Fire an event. ++ synchronized (entries) { ++ for (Iterator i = entries.iterator(); i.hasNext();) { ++ String entry = (String)i.next(); ++ if (entry.toLowerCase().equals(StringUtils.parseBareAddress(key).toLowerCase())) { ++ fireEvent(EVENT_PRESENCE_CHANGED, packet); ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ /** ++ * Listens for all roster packets and processes them. ++ */ ++ private class AgentStatusListener implements PacketListener { ++ ++ public void processPacket(Packet packet) { ++ if (packet instanceof AgentStatusRequest) { ++ AgentStatusRequest statusRequest = (AgentStatusRequest)packet; ++ for (Iterator i = statusRequest.getAgents().iterator(); i.hasNext();) { ++ AgentStatusRequest.Item item = (AgentStatusRequest.Item)i.next(); ++ String agentJID = item.getJID(); ++ if ("remove".equals(item.getType())) { ++ ++ // Removing the user from the roster, so remove any presence information ++ // about them. ++ String key = StringUtils.parseName(StringUtils.parseName(agentJID) + "@" + ++ StringUtils.parseServer(agentJID)); ++ presenceMap.remove(key); ++ // Fire event for roster listeners. ++ fireEvent(EVENT_AGENT_REMOVED, agentJID); ++ } ++ else { ++ entries.add(agentJID); ++ // Fire event for roster listeners. ++ fireEvent(EVENT_AGENT_ADDED, agentJID); ++ } ++ } ++ ++ // Mark the roster as initialized. ++ rosterInitialized = true; ++ } ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/AgentRosterListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/AgentRosterListener.java +new file mode 100644 +index 0000000..4db9203 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/AgentRosterListener.java +@@ -0,0 +1,35 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.agent; ++ ++import org.jivesoftware.smack.packet.Presence; ++ ++/** ++ * ++ * @author Matt Tucker ++ */ ++public interface AgentRosterListener { ++ ++ public void agentAdded(String jid); ++ ++ public void agentRemoved(String jid); ++ ++ public void presenceChanged(Presence presence); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/AgentSession.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/AgentSession.java +new file mode 100644 +index 0000000..340de4f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/AgentSession.java +@@ -0,0 +1,1184 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.agent; ++ ++import org.jivesoftware.smackx.workgroup.MetaData; ++import org.jivesoftware.smackx.workgroup.WorkgroupInvitation; ++import org.jivesoftware.smackx.workgroup.WorkgroupInvitationListener; ++import org.jivesoftware.smackx.workgroup.ext.history.AgentChatHistory; ++import org.jivesoftware.smackx.workgroup.ext.history.ChatMetadata; ++import org.jivesoftware.smackx.workgroup.ext.macros.MacroGroup; ++import org.jivesoftware.smackx.workgroup.ext.macros.Macros; ++import org.jivesoftware.smackx.workgroup.ext.notes.ChatNotes; ++import org.jivesoftware.smackx.workgroup.packet.*; ++import org.jivesoftware.smackx.workgroup.settings.GenericSettings; ++import org.jivesoftware.smackx.workgroup.settings.SearchSettings; ++import org.jivesoftware.smack.*; ++import org.jivesoftware.smack.filter.*; ++import org.jivesoftware.smack.packet.*; ++import org.jivesoftware.smack.util.StringUtils; ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.ReportedData; ++import org.jivesoftware.smackx.packet.MUCUser; ++ ++import java.util.*; ++ ++/** ++ * This class embodies the agent's active presence within a given workgroup. The application ++ * should have N instances of this class, where N is the number of workgroups to which the ++ * owning agent of the application belongs. This class provides all functionality that a ++ * session within a given workgroup is expected to have from an agent's perspective -- setting ++ * the status, tracking the status of queues to which the agent belongs within the workgroup, and ++ * dequeuing customers. ++ * ++ * @author Matt Tucker ++ * @author Derek DeMoro ++ */ ++public class AgentSession { ++ ++ private Connection connection; ++ ++ private String workgroupJID; ++ ++ private boolean online = false; ++ private Presence.Mode presenceMode; ++ private int maxChats; ++ private final Map metaData; ++ ++ private Map queues; ++ ++ private final List offerListeners; ++ private final List invitationListeners; ++ private final List queueUsersListeners; ++ ++ private AgentRoster agentRoster = null; ++ private TranscriptManager transcriptManager; ++ private TranscriptSearchManager transcriptSearchManager; ++ private Agent agent; ++ private PacketListener packetListener; ++ ++ /** ++ * Constructs a new agent session instance. Note, the {@link #setOnline(boolean)} ++ * method must be called with an argument of true to mark the agent ++ * as available to accept chat requests. ++ * ++ * @param connection a connection instance which must have already gone through ++ * authentication. ++ * @param workgroupJID the fully qualified JID of the workgroup. ++ */ ++ public AgentSession(String workgroupJID, Connection connection) { ++ // Login must have been done before passing in connection. ++ if (!connection.isAuthenticated()) { ++ throw new IllegalStateException("Must login to server before creating workgroup."); ++ } ++ ++ this.workgroupJID = workgroupJID; ++ this.connection = connection; ++ this.transcriptManager = new TranscriptManager(connection); ++ this.transcriptSearchManager = new TranscriptSearchManager(connection); ++ ++ this.maxChats = -1; ++ ++ this.metaData = new HashMap(); ++ ++ this.queues = new HashMap(); ++ ++ offerListeners = new ArrayList(); ++ invitationListeners = new ArrayList(); ++ queueUsersListeners = new ArrayList(); ++ ++ // Create a filter to listen for packets we're interested in. ++ OrFilter filter = new OrFilter(); ++ filter.addFilter(new PacketTypeFilter(OfferRequestProvider.OfferRequestPacket.class)); ++ filter.addFilter(new PacketTypeFilter(OfferRevokeProvider.OfferRevokePacket.class)); ++ filter.addFilter(new PacketTypeFilter(Presence.class)); ++ filter.addFilter(new PacketTypeFilter(Message.class)); ++ ++ packetListener = new PacketListener() { ++ public void processPacket(Packet packet) { ++ try { ++ handlePacket(packet); ++ } ++ catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } ++ }; ++ connection.addPacketListener(packetListener, filter); ++ // Create the agent associated to this session ++ agent = new Agent(connection, workgroupJID); ++ } ++ ++ /** ++ * Close the agent session. The underlying connection will remain opened but the ++ * packet listeners that were added by this agent session will be removed. ++ */ ++ public void close() { ++ connection.removePacketListener(packetListener); ++ } ++ ++ /** ++ * Returns the agent roster for the workgroup, which contains ++ * ++ * @return the AgentRoster ++ */ ++ public AgentRoster getAgentRoster() { ++ if (agentRoster == null) { ++ agentRoster = new AgentRoster(connection, workgroupJID); ++ } ++ ++ // This might be the first time the user has asked for the roster. If so, we ++ // want to wait up to 2 seconds for the server to send back the list of agents. ++ // This behavior shields API users from having to worry about the fact that the ++ // operation is asynchronous, although they'll still have to listen for changes ++ // to the roster. ++ int elapsed = 0; ++ while (!agentRoster.rosterInitialized && elapsed <= 2000) { ++ try { ++ Thread.sleep(500); ++ } ++ catch (Exception e) { ++ // Ignore ++ } ++ elapsed += 500; ++ } ++ return agentRoster; ++ } ++ ++ /** ++ * Returns the agent's current presence mode. ++ * ++ * @return the agent's current presence mode. ++ */ ++ public Presence.Mode getPresenceMode() { ++ return presenceMode; ++ } ++ ++ /** ++ * Returns the maximum number of chats the agent can participate in. ++ * ++ * @return the maximum number of chats the agent can participate in. ++ */ ++ public int getMaxChats() { ++ return maxChats; ++ } ++ ++ /** ++ * Returns true if the agent is online with the workgroup. ++ * ++ * @return true if the agent is online with the workgroup. ++ */ ++ public boolean isOnline() { ++ return online; ++ } ++ ++ /** ++ * Allows the addition of a new key-value pair to the agent's meta data, if the value is ++ * new data, the revised meta data will be rebroadcast in an agent's presence broadcast. ++ * ++ * @param key the meta data key ++ * @param val the non-null meta data value ++ * @throws XMPPException if an exception occurs. ++ */ ++ public void setMetaData(String key, String val) throws XMPPException { ++ synchronized (this.metaData) { ++ String oldVal = (String)this.metaData.get(key); ++ ++ if ((oldVal == null) || (!oldVal.equals(val))) { ++ metaData.put(key, val); ++ ++ setStatus(presenceMode, maxChats); ++ } ++ } ++ } ++ ++ /** ++ * Allows the removal of data from the agent's meta data, if the key represents existing data, ++ * the revised meta data will be rebroadcast in an agent's presence broadcast. ++ * ++ * @param key the meta data key. ++ * @throws XMPPException if an exception occurs. ++ */ ++ public void removeMetaData(String key) throws XMPPException { ++ synchronized (this.metaData) { ++ String oldVal = (String)metaData.remove(key); ++ ++ if (oldVal != null) { ++ setStatus(presenceMode, maxChats); ++ } ++ } ++ } ++ ++ /** ++ * Allows the retrieval of meta data for a specified key. ++ * ++ * @param key the meta data key ++ * @return the meta data value associated with the key or null if the meta-data ++ * doesn't exist.. ++ */ ++ public String getMetaData(String key) { ++ return (String)metaData.get(key); ++ } ++ ++ /** ++ * Sets whether the agent is online with the workgroup. If the user tries to go online with ++ * the workgroup but is not allowed to be an agent, an XMPPError with error code 401 will ++ * be thrown. ++ * ++ * @param online true to set the agent as online with the workgroup. ++ * @throws XMPPException if an error occurs setting the online status. ++ */ ++ public void setOnline(boolean online) throws XMPPException { ++ // If the online status hasn't changed, do nothing. ++ if (this.online == online) { ++ return; ++ } ++ ++ Presence presence; ++ ++ // If the user is going online... ++ if (online) { ++ presence = new Presence(Presence.Type.available); ++ presence.setTo(workgroupJID); ++ presence.addExtension(new DefaultPacketExtension(AgentStatus.ELEMENT_NAME, ++ AgentStatus.NAMESPACE)); ++ ++ PacketCollector collector = this.connection.createPacketCollector(new AndFilter(new PacketTypeFilter(Presence.class), new FromContainsFilter(workgroupJID))); ++ ++ connection.sendPacket(presence); ++ ++ presence = (Presence)collector.nextResult(5000); ++ collector.cancel(); ++ if (!presence.isAvailable()) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ ++ if (presence.getError() != null) { ++ throw new XMPPException(presence.getError()); ++ } ++ ++ // We can safely update this iv since we didn't get any error ++ this.online = online; ++ } ++ // Otherwise the user is going offline... ++ else { ++ // Update this iv now since we don't care at this point of any error ++ this.online = online; ++ ++ presence = new Presence(Presence.Type.unavailable); ++ presence.setTo(workgroupJID); ++ presence.addExtension(new DefaultPacketExtension(AgentStatus.ELEMENT_NAME, ++ AgentStatus.NAMESPACE)); ++ connection.sendPacket(presence); ++ } ++ } ++ ++ /** ++ * Sets the agent's current status with the workgroup. The presence mode affects ++ * how offers are routed to the agent. The possible presence modes with their ++ * meanings are as follows:

        ++ *

        ++ *

      • Presence.Mode.AVAILABLE -- (Default) the agent is available for more chats ++ * (equivalent to Presence.Mode.CHAT). ++ *
      • Presence.Mode.DO_NOT_DISTURB -- the agent is busy and should not be disturbed. ++ * However, special case, or extreme urgency chats may still be offered to the agent. ++ *
      • Presence.Mode.AWAY -- the agent is not available and should not ++ * have a chat routed to them (equivalent to Presence.Mode.EXTENDED_AWAY).
      ++ *

      ++ * The max chats value is the maximum number of chats the agent is willing to have ++ * routed to them at once. Some servers may be configured to only accept max chat ++ * values in a certain range; for example, between two and five. In that case, the ++ * maxChats value the agent sends may be adjusted by the server to a value within that ++ * range. ++ * ++ * @param presenceMode the presence mode of the agent. ++ * @param maxChats the maximum number of chats the agent is willing to accept. ++ * @throws XMPPException if an error occurs setting the agent status. ++ * @throws IllegalStateException if the agent is not online with the workgroup. ++ */ ++ public void setStatus(Presence.Mode presenceMode, int maxChats) throws XMPPException { ++ setStatus(presenceMode, maxChats, null); ++ } ++ ++ /** ++ * Sets the agent's current status with the workgroup. The presence mode affects how offers ++ * are routed to the agent. The possible presence modes with their meanings are as follows:

        ++ *

        ++ *

      • Presence.Mode.AVAILABLE -- (Default) the agent is available for more chats ++ * (equivalent to Presence.Mode.CHAT). ++ *
      • Presence.Mode.DO_NOT_DISTURB -- the agent is busy and should not be disturbed. ++ * However, special case, or extreme urgency chats may still be offered to the agent. ++ *
      • Presence.Mode.AWAY -- the agent is not available and should not ++ * have a chat routed to them (equivalent to Presence.Mode.EXTENDED_AWAY).
      ++ *

      ++ * The max chats value is the maximum number of chats the agent is willing to have routed to ++ * them at once. Some servers may be configured to only accept max chat values in a certain ++ * range; for example, between two and five. In that case, the maxChats value the agent sends ++ * may be adjusted by the server to a value within that range. ++ * ++ * @param presenceMode the presence mode of the agent. ++ * @param maxChats the maximum number of chats the agent is willing to accept. ++ * @param status sets the status message of the presence update. ++ * @throws XMPPException if an error occurs setting the agent status. ++ * @throws IllegalStateException if the agent is not online with the workgroup. ++ */ ++ public void setStatus(Presence.Mode presenceMode, int maxChats, String status) ++ throws XMPPException { ++ if (!online) { ++ throw new IllegalStateException("Cannot set status when the agent is not online."); ++ } ++ ++ if (presenceMode == null) { ++ presenceMode = Presence.Mode.available; ++ } ++ this.presenceMode = presenceMode; ++ this.maxChats = maxChats; ++ ++ Presence presence = new Presence(Presence.Type.available); ++ presence.setMode(presenceMode); ++ presence.setTo(this.getWorkgroupJID()); ++ ++ if (status != null) { ++ presence.setStatus(status); ++ } ++ // Send information about max chats and current chats as a packet extension. ++ DefaultPacketExtension agentStatus = new DefaultPacketExtension(AgentStatus.ELEMENT_NAME, ++ AgentStatus.NAMESPACE); ++ agentStatus.setValue("max-chats", "" + maxChats); ++ presence.addExtension(agentStatus); ++ presence.addExtension(new MetaData(this.metaData)); ++ ++ PacketCollector collector = this.connection.createPacketCollector(new AndFilter(new PacketTypeFilter(Presence.class), new FromContainsFilter(workgroupJID))); ++ ++ this.connection.sendPacket(presence); ++ ++ presence = (Presence)collector.nextResult(5000); ++ collector.cancel(); ++ if (!presence.isAvailable()) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ ++ if (presence.getError() != null) { ++ throw new XMPPException(presence.getError()); ++ } ++ } ++ ++ /** ++ * Sets the agent's current status with the workgroup. The presence mode affects how offers ++ * are routed to the agent. The possible presence modes with their meanings are as follows:

        ++ *

        ++ *

      • Presence.Mode.AVAILABLE -- (Default) the agent is available for more chats ++ * (equivalent to Presence.Mode.CHAT). ++ *
      • Presence.Mode.DO_NOT_DISTURB -- the agent is busy and should not be disturbed. ++ * However, special case, or extreme urgency chats may still be offered to the agent. ++ *
      • Presence.Mode.AWAY -- the agent is not available and should not ++ * have a chat routed to them (equivalent to Presence.Mode.EXTENDED_AWAY).
      ++ * ++ * @param presenceMode the presence mode of the agent. ++ * @param status sets the status message of the presence update. ++ * @throws XMPPException if an error occurs setting the agent status. ++ * @throws IllegalStateException if the agent is not online with the workgroup. ++ */ ++ public void setStatus(Presence.Mode presenceMode, String status) throws XMPPException { ++ if (!online) { ++ throw new IllegalStateException("Cannot set status when the agent is not online."); ++ } ++ ++ if (presenceMode == null) { ++ presenceMode = Presence.Mode.available; ++ } ++ this.presenceMode = presenceMode; ++ ++ Presence presence = new Presence(Presence.Type.available); ++ presence.setMode(presenceMode); ++ presence.setTo(this.getWorkgroupJID()); ++ ++ if (status != null) { ++ presence.setStatus(status); ++ } ++ presence.addExtension(new MetaData(this.metaData)); ++ ++ PacketCollector collector = this.connection.createPacketCollector(new AndFilter(new PacketTypeFilter(Presence.class), ++ new FromContainsFilter(workgroupJID))); ++ ++ this.connection.sendPacket(presence); ++ ++ presence = (Presence)collector.nextResult(5000); ++ collector.cancel(); ++ if (!presence.isAvailable()) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ ++ if (presence.getError() != null) { ++ throw new XMPPException(presence.getError()); ++ } ++ } ++ ++ /** ++ * Removes a user from the workgroup queue. This is an administrative action that the ++ *

      ++ * The agent is not guaranteed of having privileges to perform this action; an exception ++ * denying the request may be thrown. ++ * ++ * @param userID the ID of the user to remove. ++ * @throws XMPPException if an exception occurs. ++ */ ++ public void dequeueUser(String userID) throws XMPPException { ++ // todo: this method simply won't work right now. ++ DepartQueuePacket departPacket = new DepartQueuePacket(this.workgroupJID); ++ ++ // PENDING ++ this.connection.sendPacket(departPacket); ++ } ++ ++ /** ++ * Returns the transcripts of a given user. The answer will contain the complete history of ++ * conversations that a user had. ++ * ++ * @param userID the id of the user to get his conversations. ++ * @return the transcripts of a given user. ++ * @throws XMPPException if an error occurs while getting the information. ++ */ ++ public Transcripts getTranscripts(String userID) throws XMPPException { ++ return transcriptManager.getTranscripts(workgroupJID, userID); ++ } ++ ++ /** ++ * Returns the full conversation transcript of a given session. ++ * ++ * @param sessionID the id of the session to get the full transcript. ++ * @return the full conversation transcript of a given session. ++ * @throws XMPPException if an error occurs while getting the information. ++ */ ++ public Transcript getTranscript(String sessionID) throws XMPPException { ++ return transcriptManager.getTranscript(workgroupJID, sessionID); ++ } ++ ++ /** ++ * Returns the Form to use for searching transcripts. It is unlikely that the server ++ * will change the form (without a restart) so it is safe to keep the returned form ++ * for future submissions. ++ * ++ * @return the Form to use for searching transcripts. ++ * @throws XMPPException if an error occurs while sending the request to the server. ++ */ ++ public Form getTranscriptSearchForm() throws XMPPException { ++ return transcriptSearchManager.getSearchForm(StringUtils.parseServer(workgroupJID)); ++ } ++ ++ /** ++ * Submits the completed form and returns the result of the transcript search. The result ++ * will include all the data returned from the server so be careful with the amount of ++ * data that the search may return. ++ * ++ * @param completedForm the filled out search form. ++ * @return the result of the transcript search. ++ * @throws XMPPException if an error occurs while submiting the search to the server. ++ */ ++ public ReportedData searchTranscripts(Form completedForm) throws XMPPException { ++ return transcriptSearchManager.submitSearch(StringUtils.parseServer(workgroupJID), ++ completedForm); ++ } ++ ++ /** ++ * Asks the workgroup for information about the occupants of the specified room. The returned ++ * information will include the real JID of the occupants, the nickname of the user in the ++ * room as well as the date when the user joined the room. ++ * ++ * @param roomID the room to get information about its occupants. ++ * @return information about the occupants of the specified room. ++ * @throws XMPPException if an error occurs while getting information from the server. ++ */ ++ public OccupantsInfo getOccupantsInfo(String roomID) throws XMPPException { ++ OccupantsInfo request = new OccupantsInfo(roomID); ++ request.setType(IQ.Type.GET); ++ request.setTo(workgroupJID); ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); ++ connection.sendPacket(request); ++ ++ OccupantsInfo response = (OccupantsInfo)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response; ++ } ++ ++ /** ++ * @return the fully-qualified name of the workgroup for which this session exists ++ */ ++ public String getWorkgroupJID() { ++ return workgroupJID; ++ } ++ ++ /** ++ * Returns the Agent associated to this session. ++ * ++ * @return the Agent associated to this session. ++ */ ++ public Agent getAgent() { ++ return agent; ++ } ++ ++ /** ++ * @param queueName the name of the queue ++ * @return an instance of WorkgroupQueue for the argument queue name, or null if none exists ++ */ ++ public WorkgroupQueue getQueue(String queueName) { ++ return queues.get(queueName); ++ } ++ ++ public Iterator getQueues() { ++ return Collections.unmodifiableMap((new HashMap(queues))).values().iterator(); ++ } ++ ++ public void addQueueUsersListener(QueueUsersListener listener) { ++ synchronized (queueUsersListeners) { ++ if (!queueUsersListeners.contains(listener)) { ++ queueUsersListeners.add(listener); ++ } ++ } ++ } ++ ++ public void removeQueueUsersListener(QueueUsersListener listener) { ++ synchronized (queueUsersListeners) { ++ queueUsersListeners.remove(listener); ++ } ++ } ++ ++ /** ++ * Adds an offer listener. ++ * ++ * @param offerListener the offer listener. ++ */ ++ public void addOfferListener(OfferListener offerListener) { ++ synchronized (offerListeners) { ++ if (!offerListeners.contains(offerListener)) { ++ offerListeners.add(offerListener); ++ } ++ } ++ } ++ ++ /** ++ * Removes an offer listener. ++ * ++ * @param offerListener the offer listener. ++ */ ++ public void removeOfferListener(OfferListener offerListener) { ++ synchronized (offerListeners) { ++ offerListeners.remove(offerListener); ++ } ++ } ++ ++ /** ++ * Adds an invitation listener. ++ * ++ * @param invitationListener the invitation listener. ++ */ ++ public void addInvitationListener(WorkgroupInvitationListener invitationListener) { ++ synchronized (invitationListeners) { ++ if (!invitationListeners.contains(invitationListener)) { ++ invitationListeners.add(invitationListener); ++ } ++ } ++ } ++ ++ /** ++ * Removes an invitation listener. ++ * ++ * @param invitationListener the invitation listener. ++ */ ++ public void removeInvitationListener(WorkgroupInvitationListener invitationListener) { ++ synchronized (invitationListeners) { ++ invitationListeners.remove(invitationListener); ++ } ++ } ++ ++ private void fireOfferRequestEvent(OfferRequestProvider.OfferRequestPacket requestPacket) { ++ Offer offer = new Offer(this.connection, this, requestPacket.getUserID(), ++ requestPacket.getUserJID(), this.getWorkgroupJID(), ++ new Date((new Date()).getTime() + (requestPacket.getTimeout() * 1000)), ++ requestPacket.getSessionID(), requestPacket.getMetaData(), requestPacket.getContent()); ++ ++ synchronized (offerListeners) { ++ for (OfferListener listener : offerListeners) { ++ listener.offerReceived(offer); ++ } ++ } ++ } ++ ++ private void fireOfferRevokeEvent(OfferRevokeProvider.OfferRevokePacket orp) { ++ RevokedOffer revokedOffer = new RevokedOffer(orp.getUserJID(), orp.getUserID(), ++ this.getWorkgroupJID(), orp.getSessionID(), orp.getReason(), new Date()); ++ ++ synchronized (offerListeners) { ++ for (OfferListener listener : offerListeners) { ++ listener.offerRevoked(revokedOffer); ++ } ++ } ++ } ++ ++ private void fireInvitationEvent(String groupChatJID, String sessionID, String body, ++ String from, Map metaData) { ++ WorkgroupInvitation invitation = new WorkgroupInvitation(connection.getUser(), groupChatJID, ++ workgroupJID, sessionID, body, from, metaData); ++ ++ synchronized (invitationListeners) { ++ for (WorkgroupInvitationListener listener : invitationListeners) { ++ listener.invitationReceived(invitation); ++ } ++ } ++ } ++ ++ private void fireQueueUsersEvent(WorkgroupQueue queue, WorkgroupQueue.Status status, ++ int averageWaitTime, Date oldestEntry, Set users) { ++ synchronized (queueUsersListeners) { ++ for (QueueUsersListener listener : queueUsersListeners) { ++ if (status != null) { ++ listener.statusUpdated(queue, status); ++ } ++ if (averageWaitTime != -1) { ++ listener.averageWaitTimeUpdated(queue, averageWaitTime); ++ } ++ if (oldestEntry != null) { ++ listener.oldestEntryUpdated(queue, oldestEntry); ++ } ++ if (users != null) { ++ listener.usersUpdated(queue, users); ++ } ++ } ++ } ++ } ++ ++ // PacketListener Implementation. ++ ++ private void handlePacket(Packet packet) { ++ if (packet instanceof OfferRequestProvider.OfferRequestPacket) { ++ // Acknowledge the IQ set. ++ IQ reply = new IQ() { ++ public String getChildElementXML() { ++ return null; ++ } ++ }; ++ reply.setPacketID(packet.getPacketID()); ++ reply.setTo(packet.getFrom()); ++ reply.setType(IQ.Type.RESULT); ++ connection.sendPacket(reply); ++ ++ fireOfferRequestEvent((OfferRequestProvider.OfferRequestPacket)packet); ++ } ++ else if (packet instanceof Presence) { ++ Presence presence = (Presence)packet; ++ ++ // The workgroup can send us a number of different presence packets. We ++ // check for different packet extensions to see what type of presence ++ // packet it is. ++ ++ String queueName = StringUtils.parseResource(presence.getFrom()); ++ WorkgroupQueue queue = queues.get(queueName); ++ // If there isn't already an entry for the queue, create a new one. ++ if (queue == null) { ++ queue = new WorkgroupQueue(queueName); ++ queues.put(queueName, queue); ++ } ++ ++ // QueueOverview packet extensions contain basic information about a queue. ++ QueueOverview queueOverview = (QueueOverview)presence.getExtension(QueueOverview.ELEMENT_NAME, QueueOverview.NAMESPACE); ++ if (queueOverview != null) { ++ if (queueOverview.getStatus() == null) { ++ queue.setStatus(WorkgroupQueue.Status.CLOSED); ++ } ++ else { ++ queue.setStatus(queueOverview.getStatus()); ++ } ++ queue.setAverageWaitTime(queueOverview.getAverageWaitTime()); ++ queue.setOldestEntry(queueOverview.getOldestEntry()); ++ // Fire event. ++ fireQueueUsersEvent(queue, queueOverview.getStatus(), ++ queueOverview.getAverageWaitTime(), queueOverview.getOldestEntry(), ++ null); ++ return; ++ } ++ ++ // QueueDetails packet extensions contain information about the users in ++ // a queue. ++ QueueDetails queueDetails = (QueueDetails)packet.getExtension(QueueDetails.ELEMENT_NAME, QueueDetails.NAMESPACE); ++ if (queueDetails != null) { ++ queue.setUsers(queueDetails.getUsers()); ++ // Fire event. ++ fireQueueUsersEvent(queue, null, -1, null, queueDetails.getUsers()); ++ return; ++ } ++ ++ // Notify agent packets gives an overview of agent activity in a queue. ++ DefaultPacketExtension notifyAgents = (DefaultPacketExtension)presence.getExtension("notify-agents", "http://jabber.org/protocol/workgroup"); ++ if (notifyAgents != null) { ++ int currentChats = Integer.parseInt(notifyAgents.getValue("current-chats")); ++ int maxChats = Integer.parseInt(notifyAgents.getValue("max-chats")); ++ queue.setCurrentChats(currentChats); ++ queue.setMaxChats(maxChats); ++ // Fire event. ++ // TODO: might need another event for current chats and max chats of queue ++ return; ++ } ++ } ++ else if (packet instanceof Message) { ++ Message message = (Message)packet; ++ ++ // Check if a room invitation was sent and if the sender is the workgroup ++ MUCUser mucUser = (MUCUser)message.getExtension("x", ++ "http://jabber.org/protocol/muc#user"); ++ MUCUser.Invite invite = mucUser != null ? mucUser.getInvite() : null; ++ if (invite != null && workgroupJID.equals(invite.getFrom())) { ++ String sessionID = null; ++ Map metaData = null; ++ ++ SessionID sessionIDExt = (SessionID)message.getExtension(SessionID.ELEMENT_NAME, ++ SessionID.NAMESPACE); ++ if (sessionIDExt != null) { ++ sessionID = sessionIDExt.getSessionID(); ++ } ++ ++ MetaData metaDataExt = (MetaData)message.getExtension(MetaData.ELEMENT_NAME, ++ MetaData.NAMESPACE); ++ if (metaDataExt != null) { ++ metaData = metaDataExt.getMetaData(); ++ } ++ ++ this.fireInvitationEvent(message.getFrom(), sessionID, message.getBody(), ++ message.getFrom(), metaData); ++ } ++ } ++ else if (packet instanceof OfferRevokeProvider.OfferRevokePacket) { ++ // Acknowledge the IQ set. ++ IQ reply = new IQ() { ++ public String getChildElementXML() { ++ return null; ++ } ++ }; ++ reply.setPacketID(packet.getPacketID()); ++ reply.setType(IQ.Type.RESULT); ++ connection.sendPacket(reply); ++ ++ fireOfferRevokeEvent((OfferRevokeProvider.OfferRevokePacket)packet); ++ } ++ } ++ ++ /** ++ * Creates a ChatNote that will be mapped to the given chat session. ++ * ++ * @param sessionID the session id of a Chat Session. ++ * @param note the chat note to add. ++ * @throws XMPPException is thrown if an error occurs while adding the note. ++ */ ++ public void setNote(String sessionID, String note) throws XMPPException { ++ note = ChatNotes.replace(note, "\n", "\\n"); ++ note = StringUtils.escapeForXML(note); ++ ++ ChatNotes notes = new ChatNotes(); ++ notes.setType(IQ.Type.SET); ++ notes.setTo(workgroupJID); ++ notes.setSessionID(sessionID); ++ notes.setNotes(note); ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(notes.getPacketID())); ++ // Send the request ++ connection.sendPacket(notes); ++ ++ IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ } ++ ++ /** ++ * Retrieves the ChatNote associated with a given chat session. ++ * ++ * @param sessionID the sessionID of the chat session. ++ * @return the ChatNote associated with a given chat session. ++ * @throws XMPPException if an error occurs while retrieving the ChatNote. ++ */ ++ public ChatNotes getNote(String sessionID) throws XMPPException { ++ ChatNotes request = new ChatNotes(); ++ request.setType(IQ.Type.GET); ++ request.setTo(workgroupJID); ++ request.setSessionID(sessionID); ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); ++ connection.sendPacket(request); ++ ++ ChatNotes response = (ChatNotes)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response; ++ ++ } ++ ++ /** ++ * Retrieves the AgentChatHistory associated with a particular agent jid. ++ * ++ * @param jid the jid of the agent. ++ * @param maxSessions the max number of sessions to retrieve. ++ * @param startDate the starting date of sessions to retrieve. ++ * @return the chat history associated with a given jid. ++ * @throws XMPPException if an error occurs while retrieving the AgentChatHistory. ++ */ ++ public AgentChatHistory getAgentHistory(String jid, int maxSessions, Date startDate) throws XMPPException { ++ AgentChatHistory request; ++ if (startDate != null) { ++ request = new AgentChatHistory(jid, maxSessions, startDate); ++ } ++ else { ++ request = new AgentChatHistory(jid, maxSessions); ++ } ++ ++ request.setType(IQ.Type.GET); ++ request.setTo(workgroupJID); ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); ++ connection.sendPacket(request); ++ ++ AgentChatHistory response = (AgentChatHistory)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response; ++ } ++ ++ /** ++ * Asks the workgroup for it's Search Settings. ++ * ++ * @return SearchSettings the search settings for this workgroup. ++ * @throws XMPPException if an error occurs while getting information from the server. ++ */ ++ public SearchSettings getSearchSettings() throws XMPPException { ++ SearchSettings request = new SearchSettings(); ++ request.setType(IQ.Type.GET); ++ request.setTo(workgroupJID); ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); ++ connection.sendPacket(request); ++ ++ ++ SearchSettings response = (SearchSettings)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response; ++ } ++ ++ /** ++ * Asks the workgroup for it's Global Macros. ++ * ++ * @param global true to retrieve global macros, otherwise false for personal macros. ++ * @return MacroGroup the root macro group. ++ * @throws XMPPException if an error occurs while getting information from the server. ++ */ ++ public MacroGroup getMacros(boolean global) throws XMPPException { ++ Macros request = new Macros(); ++ request.setType(IQ.Type.GET); ++ request.setTo(workgroupJID); ++ request.setPersonal(!global); ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); ++ connection.sendPacket(request); ++ ++ ++ Macros response = (Macros)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response.getRootGroup(); ++ } ++ ++ /** ++ * Persists the Personal Macro for an agent. ++ * ++ * @param group the macro group to save. ++ * @throws XMPPException if an error occurs while getting information from the server. ++ */ ++ public void saveMacros(MacroGroup group) throws XMPPException { ++ Macros request = new Macros(); ++ request.setType(IQ.Type.SET); ++ request.setTo(workgroupJID); ++ request.setPersonal(true); ++ request.setPersonalMacroGroup(group); ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); ++ connection.sendPacket(request); ++ ++ ++ IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ } ++ ++ /** ++ * Query for metadata associated with a session id. ++ * ++ * @param sessionID the sessionID to query for. ++ * @return Map a map of all metadata associated with the sessionID. ++ * @throws XMPPException if an error occurs while getting information from the server. ++ */ ++ public Map getChatMetadata(String sessionID) throws XMPPException { ++ ChatMetadata request = new ChatMetadata(); ++ request.setType(IQ.Type.GET); ++ request.setTo(workgroupJID); ++ request.setSessionID(sessionID); ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); ++ connection.sendPacket(request); ++ ++ ++ ChatMetadata response = (ChatMetadata)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response.getMetadata(); ++ } ++ ++ /** ++ * Invites a user or agent to an existing session support. The provided invitee's JID can be of ++ * a user, an agent, a queue or a workgroup. In the case of a queue or a workgroup the workgroup service ++ * will decide the best agent to receive the invitation.

      ++ * ++ * This method will return either when the service returned an ACK of the request or if an error occured ++ * while requesting the invitation. After sending the ACK the service will send the invitation to the target ++ * entity. When dealing with agents the common sequence of offer-response will be followed. However, when ++ * sending an invitation to a user a standard MUC invitation will be sent.

      ++ * ++ * The agent or user that accepted the offer MUST join the room. Failing to do so will make ++ * the invitation to fail. The inviter will eventually receive a message error indicating that the invitee ++ * accepted the offer but failed to join the room. ++ * ++ * Different situations may lead to a failed invitation. Possible cases are: 1) all agents rejected the ++ * offer and ther are no agents available, 2) the agent that accepted the offer failed to join the room or ++ * 2) the user that received the MUC invitation never replied or joined the room. In any of these cases ++ * (or other failing cases) the inviter will get an error message with the failed notification. ++ * ++ * @param type type of entity that will get the invitation. ++ * @param invitee JID of entity that will get the invitation. ++ * @param sessionID ID of the support session that the invitee is being invited. ++ * @param reason the reason of the invitation. ++ * @throws XMPPException if the sender of the invitation is not an agent or the service failed to process ++ * the request. ++ */ ++ public void sendRoomInvitation(RoomInvitation.Type type, String invitee, String sessionID, String reason) ++ throws XMPPException { ++ final RoomInvitation invitation = new RoomInvitation(type, invitee, sessionID, reason); ++ IQ iq = new IQ() { ++ ++ public String getChildElementXML() { ++ return invitation.toXML(); ++ } ++ }; ++ iq.setType(IQ.Type.SET); ++ iq.setTo(workgroupJID); ++ iq.setFrom(connection.getUser()); ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(iq.getPacketID())); ++ connection.sendPacket(iq); ++ ++ IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ } ++ ++ /** ++ * Transfer an existing session support to another user or agent. The provided invitee's JID can be of ++ * a user, an agent, a queue or a workgroup. In the case of a queue or a workgroup the workgroup service ++ * will decide the best agent to receive the invitation.

      ++ * ++ * This method will return either when the service returned an ACK of the request or if an error occured ++ * while requesting the transfer. After sending the ACK the service will send the invitation to the target ++ * entity. When dealing with agents the common sequence of offer-response will be followed. However, when ++ * sending an invitation to a user a standard MUC invitation will be sent.

      ++ * ++ * Once the invitee joins the support room the workgroup service will kick the inviter from the room.

      ++ * ++ * Different situations may lead to a failed transfers. Possible cases are: 1) all agents rejected the ++ * offer and there are no agents available, 2) the agent that accepted the offer failed to join the room ++ * or 2) the user that received the MUC invitation never replied or joined the room. In any of these cases ++ * (or other failing cases) the inviter will get an error message with the failed notification. ++ * ++ * @param type type of entity that will get the invitation. ++ * @param invitee JID of entity that will get the invitation. ++ * @param sessionID ID of the support session that the invitee is being invited. ++ * @param reason the reason of the invitation. ++ * @throws XMPPException if the sender of the invitation is not an agent or the service failed to process ++ * the request. ++ */ ++ public void sendRoomTransfer(RoomTransfer.Type type, String invitee, String sessionID, String reason) ++ throws XMPPException { ++ final RoomTransfer transfer = new RoomTransfer(type, invitee, sessionID, reason); ++ IQ iq = new IQ() { ++ ++ public String getChildElementXML() { ++ return transfer.toXML(); ++ } ++ }; ++ iq.setType(IQ.Type.SET); ++ iq.setTo(workgroupJID); ++ iq.setFrom(connection.getUser()); ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(iq.getPacketID())); ++ connection.sendPacket(iq); ++ ++ IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ } ++ ++ /** ++ * Returns the generic metadata of the workgroup the agent belongs to. ++ * ++ * @param con the Connection to use. ++ * @param query an optional query object used to tell the server what metadata to retrieve. This can be null. ++ * @throws XMPPException if an error occurs while sending the request to the server. ++ * @return the settings for the workgroup. ++ */ ++ public GenericSettings getGenericSettings(Connection con, String query) throws XMPPException { ++ GenericSettings setting = new GenericSettings(); ++ setting.setType(IQ.Type.GET); ++ setting.setTo(workgroupJID); ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(setting.getPacketID())); ++ connection.sendPacket(setting); ++ ++ GenericSettings response = (GenericSettings)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response; ++ } ++ ++ public boolean hasMonitorPrivileges(Connection con) throws XMPPException { ++ MonitorPacket request = new MonitorPacket(); ++ request.setType(IQ.Type.GET); ++ request.setTo(workgroupJID); ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); ++ connection.sendPacket(request); ++ ++ MonitorPacket response = (MonitorPacket)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response.isMonitor(); ++ ++ } ++ ++ public void makeRoomOwner(Connection con, String sessionID) throws XMPPException { ++ MonitorPacket request = new MonitorPacket(); ++ request.setType(IQ.Type.SET); ++ request.setTo(workgroupJID); ++ request.setSessionID(sessionID); ++ ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); ++ connection.sendPacket(request); ++ ++ Packet response = collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/InvitationRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/InvitationRequest.java +new file mode 100644 +index 0000000..16b324a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/InvitationRequest.java +@@ -0,0 +1,62 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.agent; ++ ++/** ++ * Request sent by an agent to invite another agent or user. ++ * ++ * @author Gaston Dombiak ++ */ ++public class InvitationRequest extends OfferContent { ++ ++ private String inviter; ++ private String room; ++ private String reason; ++ ++ public InvitationRequest(String inviter, String room, String reason) { ++ this.inviter = inviter; ++ this.room = room; ++ this.reason = reason; ++ } ++ ++ public String getInviter() { ++ return inviter; ++ } ++ ++ public String getRoom() { ++ return room; ++ } ++ ++ public String getReason() { ++ return reason; ++ } ++ ++ boolean isUserRequest() { ++ return false; ++ } ++ ++ boolean isInvitation() { ++ return true; ++ } ++ ++ boolean isTransfer() { ++ return false; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/Offer.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/Offer.java +new file mode 100644 +index 0000000..cb8dcfb +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/Offer.java +@@ -0,0 +1,222 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.agent; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++ ++import java.util.Date; ++import java.util.Map; ++ ++/** ++ * A class embodying the semantic agent chat offer; specific instances allow the acceptance or ++ * rejecting of the offer.
      ++ * ++ * @author Matt Tucker ++ * @author loki der quaeler ++ * @author Derek DeMoro ++ */ ++public class Offer { ++ ++ private Connection connection; ++ private AgentSession session; ++ ++ private String sessionID; ++ private String userJID; ++ private String userID; ++ private String workgroupName; ++ private Date expiresDate; ++ private Map metaData; ++ private OfferContent content; ++ ++ private boolean accepted = false; ++ private boolean rejected = false; ++ ++ /** ++ * Creates a new offer. ++ * ++ * @param conn the XMPP connection with which the issuing session was created. ++ * @param agentSession the agent session instance through which this offer was issued. ++ * @param userID the userID of the user from which the offer originates. ++ * @param userJID the XMPP address of the user from which the offer originates. ++ * @param workgroupName the fully qualified name of the workgroup. ++ * @param expiresDate the date at which this offer expires. ++ * @param sessionID the session id associated with the offer. ++ * @param metaData the metadata associated with the offer. ++ * @param content content of the offer. The content explains the reason for the offer ++ * (e.g. user request, transfer) ++ */ ++ Offer(Connection conn, AgentSession agentSession, String userID, ++ String userJID, String workgroupName, Date expiresDate, ++ String sessionID, Map metaData, OfferContent content) ++ { ++ this.connection = conn; ++ this.session = agentSession; ++ this.userID = userID; ++ this.userJID = userJID; ++ this.workgroupName = workgroupName; ++ this.expiresDate = expiresDate; ++ this.sessionID = sessionID; ++ this.metaData = metaData; ++ this.content = content; ++ } ++ ++ /** ++ * Accepts the offer. ++ */ ++ public void accept() { ++ Packet acceptPacket = new AcceptPacket(this.session.getWorkgroupJID()); ++ connection.sendPacket(acceptPacket); ++ // TODO: listen for a reply. ++ accepted = true; ++ } ++ ++ /** ++ * Rejects the offer. ++ */ ++ public void reject() { ++ RejectPacket rejectPacket = new RejectPacket(this.session.getWorkgroupJID()); ++ connection.sendPacket(rejectPacket); ++ // TODO: listen for a reply. ++ rejected = true; ++ } ++ ++ /** ++ * Returns the userID that the offer originates from. In most cases, the ++ * userID will simply be the JID of the requesting user. However, users can ++ * also manually specify a userID for their request. In that case, that value will ++ * be returned. ++ * ++ * @return the userID of the user from which the offer originates. ++ */ ++ public String getUserID() { ++ return userID; ++ } ++ ++ /** ++ * Returns the JID of the user that made the offer request. ++ * ++ * @return the user's JID. ++ */ ++ public String getUserJID() { ++ return userJID; ++ } ++ ++ /** ++ * The fully qualified name of the workgroup (eg support@example.com). ++ * ++ * @return the name of the workgroup. ++ */ ++ public String getWorkgroupName() { ++ return this.workgroupName; ++ } ++ ++ /** ++ * The date when the offer will expire. The agent must {@link #accept()} ++ * the offer before the expiration date or the offer will lapse and be ++ * routed to another agent. Alternatively, the agent can {@link #reject()} ++ * the offer at any time if they don't wish to accept it.. ++ * ++ * @return the date at which this offer expires. ++ */ ++ public Date getExpiresDate() { ++ return this.expiresDate; ++ } ++ ++ /** ++ * The session ID associated with the offer. ++ * ++ * @return the session id associated with the offer. ++ */ ++ public String getSessionID() { ++ return this.sessionID; ++ } ++ ++ /** ++ * The meta-data associated with the offer. ++ * ++ * @return the offer meta-data. ++ */ ++ public Map getMetaData() { ++ return this.metaData; ++ } ++ ++ /** ++ * Returns the content of the offer. The content explains the reason for the offer ++ * (e.g. user request, transfer) ++ * ++ * @return the content of the offer. ++ */ ++ public OfferContent getContent() { ++ return content; ++ } ++ ++ /** ++ * Returns true if the agent accepted this offer. ++ * ++ * @return true if the agent accepted this offer. ++ */ ++ public boolean isAccepted() { ++ return accepted; ++ } ++ ++ /** ++ * Return true if the agent rejected this offer. ++ * ++ * @return true if the agent rejected this offer. ++ */ ++ public boolean isRejected() { ++ return rejected; ++ } ++ ++ /** ++ * Packet for rejecting offers. ++ */ ++ private class RejectPacket extends IQ { ++ ++ RejectPacket(String workgroup) { ++ this.setTo(workgroup); ++ this.setType(IQ.Type.SET); ++ } ++ ++ public String getChildElementXML() { ++ return ""; ++ } ++ } ++ ++ /** ++ * Packet for accepting an offer. ++ */ ++ private class AcceptPacket extends IQ { ++ ++ AcceptPacket(String workgroup) { ++ this.setTo(workgroup); ++ this.setType(IQ.Type.SET); ++ } ++ ++ public String getChildElementXML() { ++ return ""; ++ } ++ } ++ ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferConfirmation.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferConfirmation.java +new file mode 100644 +index 0000000..f55d588 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferConfirmation.java +@@ -0,0 +1,114 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.agent; ++ ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++ ++public class OfferConfirmation extends IQ { ++ private String userJID; ++ private long sessionID; ++ ++ public String getUserJID() { ++ return userJID; ++ } ++ ++ public void setUserJID(String userJID) { ++ this.userJID = userJID; ++ } ++ ++ public long getSessionID() { ++ return sessionID; ++ } ++ ++ public void setSessionID(long sessionID) { ++ this.sessionID = sessionID; ++ } ++ ++ ++ public void notifyService(Connection con, String workgroup, String createdRoomName) { ++ NotifyServicePacket packet = new NotifyServicePacket(workgroup, createdRoomName); ++ con.sendPacket(packet); ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ public static class Provider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ final OfferConfirmation confirmation = new OfferConfirmation(); ++ ++ boolean done = false; ++ while (!done) { ++ parser.next(); ++ String elementName = parser.getName(); ++ if (parser.getEventType() == XmlPullParser.START_TAG && "user-jid".equals(elementName)) { ++ try { ++ confirmation.setUserJID(parser.nextText()); ++ } ++ catch (NumberFormatException nfe) { ++ } ++ } ++ else if (parser.getEventType() == XmlPullParser.START_TAG && "session-id".equals(elementName)) { ++ try { ++ confirmation.setSessionID(Long.valueOf(parser.nextText())); ++ } ++ catch (NumberFormatException nfe) { ++ } ++ } ++ else if (parser.getEventType() == XmlPullParser.END_TAG && "offer-confirmation".equals(elementName)) { ++ done = true; ++ } ++ } ++ ++ ++ return confirmation; ++ } ++ } ++ ++ ++ /** ++ * Packet for notifying server of RoomName ++ */ ++ private class NotifyServicePacket extends IQ { ++ String roomName; ++ ++ NotifyServicePacket(String workgroup, String roomName) { ++ this.setTo(workgroup); ++ this.setType(IQ.Type.RESULT); ++ ++ this.roomName = roomName; ++ } ++ ++ public String getChildElementXML() { ++ return ""; ++ } ++ } ++ ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferConfirmationListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferConfirmationListener.java +new file mode 100644 +index 0000000..fb10550 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferConfirmationListener.java +@@ -0,0 +1,32 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.agent; ++ ++public interface OfferConfirmationListener { ++ ++ ++ /** ++ * The implementing class instance will be notified via this when the AgentSession has confirmed ++ * the acceptance of the Offer. The instance will then have the ability to create the room and ++ * send the service the room name created for tracking. ++ * ++ * @param confirmedOffer the ConfirmedOffer ++ */ ++ void offerConfirmed(OfferConfirmation confirmedOffer); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferContent.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferContent.java +new file mode 100644 +index 0000000..a11ddc3 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferContent.java +@@ -0,0 +1,55 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.agent; ++ ++/** ++ * Type of content being included in the offer. The content actually explains the reason ++ * the agent is getting an offer. ++ * ++ * @author Gaston Dombiak ++ */ ++public abstract class OfferContent { ++ ++ /** ++ * Returns true if the content of the offer is related to a user request. This is the ++ * most common type of offers an agent should receive. ++ * ++ * @return true if the content of the offer is related to a user request. ++ */ ++ abstract boolean isUserRequest(); ++ ++ /** ++ * Returns true if the content of the offer is related to a room invitation made by another ++ * agent. This type of offer include the room to join, metadata sent by the user while joining ++ * the queue and the reason why the agent is being invited. ++ * ++ * @return true if the content of the offer is related to a room invitation made by another agent. ++ */ ++ abstract boolean isInvitation(); ++ ++ /** ++ * Returns true if the content of the offer is related to a service transfer made by another ++ * agent. This type of offers include the room to join, metadata sent by the user while joining the ++ * queue and the reason why the agent is receiving the transfer offer. ++ * ++ * @return true if the content of the offer is related to a service transfer made by another agent. ++ */ ++ abstract boolean isTransfer(); ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferListener.java +new file mode 100644 +index 0000000..5efde99 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferListener.java +@@ -0,0 +1,49 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.agent; ++ ++/** ++ * An interface which all classes interested in hearing about chat offers associated to a particular ++ * AgentSession instance should implement.
      ++ * ++ * @author Matt Tucker ++ * @author loki der quaeler ++ * @see org.jivesoftware.smackx.workgroup.agent.AgentSession ++ */ ++public interface OfferListener { ++ ++ /** ++ * The implementing class instance will be notified via this when the AgentSession has received ++ * an offer for a chat. The instance will then have the ability to accept, reject, or ignore ++ * the request (resulting in a revocation-by-timeout). ++ * ++ * @param request the Offer instance embodying the details of the offer ++ */ ++ public void offerReceived (Offer request); ++ ++ /** ++ * The implementing class instance will be notified via this when the AgentSessino has received ++ * a revocation of a previously extended offer. ++ * ++ * @param revokedOffer the RevokedOffer instance embodying the details of the revoked offer ++ */ ++ public void offerRevoked (RevokedOffer revokedOffer); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/QueueUsersListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/QueueUsersListener.java +new file mode 100644 +index 0000000..620a58c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/QueueUsersListener.java +@@ -0,0 +1,58 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.agent; ++ ++import java.util.Date; ++import java.util.Set; ++ ++public interface QueueUsersListener { ++ ++ /** ++ * The status of the queue was updated. ++ * ++ * @param queue the workgroup queue. ++ * @param status the status of queue. ++ */ ++ public void statusUpdated(WorkgroupQueue queue, WorkgroupQueue.Status status); ++ ++ /** ++ * The average wait time of the queue was updated. ++ * ++ * @param queue the workgroup queue. ++ * @param averageWaitTime the average wait time of the queue. ++ */ ++ public void averageWaitTimeUpdated(WorkgroupQueue queue, int averageWaitTime); ++ ++ /** ++ * The date of oldest entry waiting in the queue was updated. ++ * ++ * @param queue the workgroup queue. ++ * @param oldestEntry the date of the oldest entry waiting in the queue. ++ */ ++ public void oldestEntryUpdated(WorkgroupQueue queue, Date oldestEntry); ++ ++ /** ++ * The list of users waiting in the queue was updated. ++ * ++ * @param queue the workgroup queue. ++ * @param users the list of users waiting in the queue. ++ */ ++ public void usersUpdated(WorkgroupQueue queue, Set users); ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/RevokedOffer.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/RevokedOffer.java +new file mode 100644 +index 0000000..dab4d91 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/RevokedOffer.java +@@ -0,0 +1,98 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.agent; ++ ++import java.util.Date; ++ ++/** ++ * An immutable simple class to embody the information concerning a revoked offer, this is namely ++ * the reason, the workgroup, the userJID, and the timestamp which the message was received.
      ++ * ++ * @author loki der quaeler ++ */ ++public class RevokedOffer { ++ ++ private String userJID; ++ private String userID; ++ private String workgroupName; ++ private String sessionID; ++ private String reason; ++ private Date timestamp; ++ ++ /** ++ * ++ * @param userJID the JID of the user for which this revocation was issued. ++ * @param userID the user ID of the user for which this revocation was issued. ++ * @param workgroupName the fully qualified name of the workgroup ++ * @param sessionID the session id attributed to this chain of packets ++ * @param reason the server issued message as to why this revocation was issued. ++ * @param timestamp the timestamp at which the revocation was issued ++ */ ++ RevokedOffer(String userJID, String userID, String workgroupName, String sessionID, ++ String reason, Date timestamp) { ++ super(); ++ ++ this.userJID = userJID; ++ this.userID = userID; ++ this.workgroupName = workgroupName; ++ this.sessionID = sessionID; ++ this.reason = reason; ++ this.timestamp = timestamp; ++ } ++ ++ public String getUserJID() { ++ return userJID; ++ } ++ ++ /** ++ * @return the jid of the user for which this revocation was issued ++ */ ++ public String getUserID() { ++ return this.userID; ++ } ++ ++ /** ++ * @return the fully qualified name of the workgroup ++ */ ++ public String getWorkgroupName() { ++ return this.workgroupName; ++ } ++ ++ /** ++ * @return the session id which will associate all packets for the pending chat ++ */ ++ public String getSessionID() { ++ return this.sessionID; ++ } ++ ++ /** ++ * @return the server issued message as to why this revocation was issued ++ */ ++ public String getReason() { ++ return this.reason; ++ } ++ ++ /** ++ * @return the timestamp at which the revocation was issued ++ */ ++ public Date getTimestamp() { ++ return this.timestamp; ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/TranscriptManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/TranscriptManager.java +new file mode 100644 +index 0000000..8a3801f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/TranscriptManager.java +@@ -0,0 +1,100 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.agent; ++ ++import org.jivesoftware.smackx.workgroup.packet.Transcript; ++import org.jivesoftware.smackx.workgroup.packet.Transcripts; ++import org.jivesoftware.smack.PacketCollector; ++import org.jivesoftware.smack.SmackConfiguration; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.PacketIDFilter; ++ ++/** ++ * A TranscriptManager helps to retrieve the full conversation transcript of a given session ++ * {@link #getTranscript(String, String)} or to retrieve a list with the summary of all the ++ * conversations that a user had {@link #getTranscripts(String, String)}. ++ * ++ * @author Gaston Dombiak ++ */ ++public class TranscriptManager { ++ private Connection connection; ++ ++ public TranscriptManager(Connection connection) { ++ this.connection = connection; ++ } ++ ++ /** ++ * Returns the full conversation transcript of a given session. ++ * ++ * @param sessionID the id of the session to get the full transcript. ++ * @param workgroupJID the JID of the workgroup that will process the request. ++ * @return the full conversation transcript of a given session. ++ * @throws XMPPException if an error occurs while getting the information. ++ */ ++ public Transcript getTranscript(String workgroupJID, String sessionID) throws XMPPException { ++ Transcript request = new Transcript(sessionID); ++ request.setTo(workgroupJID); ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); ++ // Send the request ++ connection.sendPacket(request); ++ ++ Transcript response = (Transcript) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response; ++ } ++ ++ /** ++ * Returns the transcripts of a given user. The answer will contain the complete history of ++ * conversations that a user had. ++ * ++ * @param userID the id of the user to get his conversations. ++ * @param workgroupJID the JID of the workgroup that will process the request. ++ * @return the transcripts of a given user. ++ * @throws XMPPException if an error occurs while getting the information. ++ */ ++ public Transcripts getTranscripts(String workgroupJID, String userID) throws XMPPException { ++ Transcripts request = new Transcripts(userID); ++ request.setTo(workgroupJID); ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); ++ // Send the request ++ connection.sendPacket(request); ++ ++ Transcripts response = (Transcripts) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/TranscriptSearchManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/TranscriptSearchManager.java +new file mode 100644 +index 0000000..8260cd6 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/TranscriptSearchManager.java +@@ -0,0 +1,111 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.agent; ++ ++import org.jivesoftware.smackx.workgroup.packet.TranscriptSearch; ++import org.jivesoftware.smack.PacketCollector; ++import org.jivesoftware.smack.SmackConfiguration; ++import org.jivesoftware.smack.Connection; ++import org.jivesoftware.smack.XMPPException; ++import org.jivesoftware.smack.filter.PacketIDFilter; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.ReportedData; ++ ++/** ++ * A TranscriptSearchManager helps to retrieve the form to use for searching transcripts ++ * {@link #getSearchForm(String)} or to submit a search form and return the results of ++ * the search {@link #submitSearch(String, Form)}. ++ * ++ * @author Gaston Dombiak ++ */ ++public class TranscriptSearchManager { ++ private Connection connection; ++ ++ public TranscriptSearchManager(Connection connection) { ++ this.connection = connection; ++ } ++ ++ /** ++ * Returns the Form to use for searching transcripts. It is unlikely that the server ++ * will change the form (without a restart) so it is safe to keep the returned form ++ * for future submissions. ++ * ++ * @param serviceJID the address of the workgroup service. ++ * @return the Form to use for searching transcripts. ++ * @throws XMPPException if an error occurs while sending the request to the server. ++ */ ++ public Form getSearchForm(String serviceJID) throws XMPPException { ++ TranscriptSearch search = new TranscriptSearch(); ++ search.setType(IQ.Type.GET); ++ search.setTo(serviceJID); ++ ++ PacketCollector collector = connection.createPacketCollector( ++ new PacketIDFilter(search.getPacketID())); ++ connection.sendPacket(search); ++ ++ TranscriptSearch response = (TranscriptSearch) collector.nextResult( ++ SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return Form.getFormFrom(response); ++ } ++ ++ /** ++ * Submits the completed form and returns the result of the transcript search. The result ++ * will include all the data returned from the server so be careful with the amount of ++ * data that the search may return. ++ * ++ * @param serviceJID the address of the workgroup service. ++ * @param completedForm the filled out search form. ++ * @return the result of the transcript search. ++ * @throws XMPPException if an error occurs while submiting the search to the server. ++ */ ++ public ReportedData submitSearch(String serviceJID, Form completedForm) throws XMPPException { ++ TranscriptSearch search = new TranscriptSearch(); ++ search.setType(IQ.Type.GET); ++ search.setTo(serviceJID); ++ search.addExtension(completedForm.getDataFormToSend()); ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(search.getPacketID())); ++ connection.sendPacket(search); ++ ++ TranscriptSearch response = (TranscriptSearch) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return ReportedData.getReportedDataFrom(response); ++ } ++} ++ ++ +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/TransferRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/TransferRequest.java +new file mode 100644 +index 0000000..a3abbaa +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/TransferRequest.java +@@ -0,0 +1,62 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.agent; ++ ++/** ++ * Request sent by an agent to transfer a support session to another agent or user. ++ * ++ * @author Gaston Dombiak ++ */ ++public class TransferRequest extends OfferContent { ++ ++ private String inviter; ++ private String room; ++ private String reason; ++ ++ public TransferRequest(String inviter, String room, String reason) { ++ this.inviter = inviter; ++ this.room = room; ++ this.reason = reason; ++ } ++ ++ public String getInviter() { ++ return inviter; ++ } ++ ++ public String getRoom() { ++ return room; ++ } ++ ++ public String getReason() { ++ return reason; ++ } ++ ++ boolean isUserRequest() { ++ return false; ++ } ++ ++ boolean isInvitation() { ++ return false; ++ } ++ ++ boolean isTransfer() { ++ return true; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/UserRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/UserRequest.java +new file mode 100644 +index 0000000..ccaaaf3 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/UserRequest.java +@@ -0,0 +1,47 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.agent; ++ ++/** ++ * Requests made by users to get support by some agent. ++ * ++ * @author Gaston Dombiak ++ */ ++public class UserRequest extends OfferContent { ++ // TODO Do we want to use a singleton? Should we store the userID here? ++ private static UserRequest instance = new UserRequest(); ++ ++ public static OfferContent getInstance() { ++ return instance; ++ } ++ ++ boolean isUserRequest() { ++ return true; ++ } ++ ++ boolean isInvitation() { ++ return false; ++ } ++ ++ boolean isTransfer() { ++ return false; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/WorkgroupQueue.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/WorkgroupQueue.java +new file mode 100644 +index 0000000..696d892 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/WorkgroupQueue.java +@@ -0,0 +1,222 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.agent; ++ ++import java.util.*; ++ ++/** ++ * A queue in a workgroup, which is a pool of agents that are routed a specific type of ++ * chat request. ++ */ ++public class WorkgroupQueue { ++ ++ private String name; ++ private Status status = Status.CLOSED; ++ ++ private int averageWaitTime = -1; ++ private Date oldestEntry = null; ++ private Set users = Collections.EMPTY_SET; ++ ++ private int maxChats = 0; ++ private int currentChats = 0; ++ ++ /** ++ * Creates a new workgroup queue instance. ++ * ++ * @param name the name of the queue. ++ */ ++ WorkgroupQueue(String name) { ++ this.name = name; ++ } ++ ++ /** ++ * Returns the name of the queue. ++ * ++ * @return the name of the queue. ++ */ ++ public String getName() { ++ return name; ++ } ++ ++ /** ++ * Returns the status of the queue. ++ * ++ * @return the status of the queue. ++ */ ++ public Status getStatus() { ++ return status; ++ } ++ ++ void setStatus(Status status) { ++ this.status = status; ++ } ++ ++ /** ++ * Returns the number of users waiting in the queue waiting to be routed to ++ * an agent. ++ * ++ * @return the number of users waiting in the queue. ++ */ ++ public int getUserCount() { ++ if (users == null) { ++ return 0; ++ } ++ return users.size(); ++ } ++ ++ /** ++ * Returns an Iterator for the users in the queue waiting to be routed to ++ * an agent (QueueUser instances). ++ * ++ * @return an Iterator for the users waiting in the queue. ++ */ ++ public Iterator getUsers() { ++ if (users == null) { ++ return Collections.EMPTY_SET.iterator(); ++ } ++ return Collections.unmodifiableSet(users).iterator(); ++ } ++ ++ void setUsers(Set users) { ++ this.users = users; ++ } ++ ++ /** ++ * Returns the average amount of time users wait in the queue before being ++ * routed to an agent. If average wait time info isn't available, -1 will ++ * be returned. ++ * ++ * @return the average wait time ++ */ ++ public int getAverageWaitTime() { ++ return averageWaitTime; ++ } ++ ++ void setAverageWaitTime(int averageTime) { ++ this.averageWaitTime = averageTime; ++ } ++ ++ /** ++ * Returns the date of the oldest request waiting in the queue. If there ++ * are no requests waiting to be routed, this method will return null. ++ * ++ * @return the date of the oldest request in the queue. ++ */ ++ public Date getOldestEntry() { ++ return oldestEntry; ++ } ++ ++ void setOldestEntry(Date oldestEntry) { ++ this.oldestEntry = oldestEntry; ++ } ++ ++ /** ++ * Returns the maximum number of simultaneous chats the queue can handle. ++ * ++ * @return the max number of chats the queue can handle. ++ */ ++ public int getMaxChats() { ++ return maxChats; ++ } ++ ++ void setMaxChats(int maxChats) { ++ this.maxChats = maxChats; ++ } ++ ++ /** ++ * Returns the current number of active chat sessions in the queue. ++ * ++ * @return the current number of active chat sessions in the queue. ++ */ ++ public int getCurrentChats() { ++ return currentChats; ++ } ++ ++ void setCurrentChats(int currentChats) { ++ this.currentChats = currentChats; ++ } ++ ++ /** ++ * A class to represent the status of the workgroup. The possible values are: ++ * ++ *

        ++ *
      • WorkgroupQueue.Status.OPEN -- the queue is active and accepting new chat requests. ++ *
      • WorkgroupQueue.Status.ACTIVE -- the queue is active but NOT accepting new chat ++ * requests. ++ *
      • WorkgroupQueue.Status.CLOSED -- the queue is NOT active and NOT accepting new ++ * chat requests. ++ *
      ++ */ ++ public static class Status { ++ ++ /** ++ * The queue is active and accepting new chat requests. ++ */ ++ public static final Status OPEN = new Status("open"); ++ ++ /** ++ * The queue is active but NOT accepting new chat requests. This state might ++ * occur when the workgroup has closed because regular support hours have closed, ++ * but there are still several requests left in the queue. ++ */ ++ public static final Status ACTIVE = new Status("active"); ++ ++ /** ++ * The queue is NOT active and NOT accepting new chat requests. ++ */ ++ public static final Status CLOSED = new Status("closed"); ++ ++ /** ++ * Converts a String into the corresponding status. Valid String values ++ * that can be converted to a status are: "open", "active", and "closed". ++ * ++ * @param type the String value to covert. ++ * @return the corresponding Type. ++ */ ++ public static Status fromString(String type) { ++ if (type == null) { ++ return null; ++ } ++ type = type.toLowerCase(); ++ if (OPEN.toString().equals(type)) { ++ return OPEN; ++ } ++ else if (ACTIVE.toString().equals(type)) { ++ return ACTIVE; ++ } ++ else if (CLOSED.toString().equals(type)) { ++ return CLOSED; ++ } ++ else { ++ return null; ++ } ++ } ++ ++ private String value; ++ ++ private Status(String value) { ++ this.value = value; ++ } ++ ++ public String toString() { ++ return value; ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/forms/WorkgroupForm.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/forms/WorkgroupForm.java +new file mode 100644 +index 0000000..f2dc08e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/forms/WorkgroupForm.java +@@ -0,0 +1,82 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.ext.forms; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smack.util.PacketParserUtils; ++import org.xmlpull.v1.XmlPullParser; ++ ++public class WorkgroupForm extends IQ { ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "workgroup-form"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); ++ // Add packet extensions, if any are defined. ++ buf.append(getExtensionsXML()); ++ buf.append(" "); ++ ++ return buf.toString(); ++ } ++ ++ /** ++ * An IQProvider for WebForm packets. ++ * ++ * @author Derek DeMoro ++ */ ++ public static class InternalProvider implements IQProvider { ++ ++ public InternalProvider() { ++ super(); ++ } ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ WorkgroupForm answer = new WorkgroupForm(); ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ // Parse the packet extension ++ answer.addExtension(PacketParserUtils.parsePacketExtension(parser.getName(), ++ parser.getNamespace(), parser)); ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals(ELEMENT_NAME)) { ++ done = true; ++ } ++ } ++ } ++ ++ return answer; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/history/AgentChatHistory.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/history/AgentChatHistory.java +new file mode 100644 +index 0000000..027c269 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/history/AgentChatHistory.java +@@ -0,0 +1,155 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.ext.history; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.Date; ++import java.util.List; ++ ++/** ++ * IQ provider used to retrieve individual agent information. Each chat session can be mapped ++ * to one or more jids and therefore retrievable. ++ */ ++public class AgentChatHistory extends IQ { ++ private String agentJID; ++ private int maxSessions; ++ private long startDate; ++ ++ private List agentChatSessions = new ArrayList(); ++ ++ public AgentChatHistory(String agentJID, int maxSessions, Date startDate) { ++ this.agentJID = agentJID; ++ this.maxSessions = maxSessions; ++ this.startDate = startDate.getTime(); ++ } ++ ++ public AgentChatHistory(String agentJID, int maxSessions) { ++ this.agentJID = agentJID; ++ this.maxSessions = maxSessions; ++ this.startDate = 0; ++ } ++ ++ public AgentChatHistory() { ++ } ++ ++ public void addChatSession(AgentChatSession chatSession) { ++ agentChatSessions.add(chatSession); ++ } ++ ++ public Collection getAgentChatSessions() { ++ return agentChatSessions; ++ } ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "chat-sessions"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns="); ++ buf.append('"'); ++ buf.append(NAMESPACE); ++ buf.append('"'); ++ buf.append(" agentJID=\"" + agentJID + "\""); ++ buf.append(" maxSessions=\"" + maxSessions + "\""); ++ buf.append(" startDate=\"" + startDate + "\""); ++ ++ buf.append("> "); ++ return buf.toString(); ++ } ++ ++ /** ++ * Packet extension provider for AgentHistory packets. ++ */ ++ public static class InternalProvider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ if (parser.getEventType() != XmlPullParser.START_TAG) { ++ throw new IllegalStateException("Parser not in proper position, or bad XML."); ++ } ++ ++ AgentChatHistory agentChatHistory = new AgentChatHistory(); ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if ((eventType == XmlPullParser.START_TAG) && ("chat-session".equals(parser.getName()))) { ++ agentChatHistory.addChatSession(parseChatSetting(parser)); ++ ++ } ++ else if (eventType == XmlPullParser.END_TAG && ELEMENT_NAME.equals(parser.getName())) { ++ done = true; ++ } ++ } ++ return agentChatHistory; ++ } ++ ++ private AgentChatSession parseChatSetting(XmlPullParser parser) throws Exception { ++ ++ boolean done = false; ++ Date date = null; ++ long duration = 0; ++ String visitorsName = null; ++ String visitorsEmail = null; ++ String sessionID = null; ++ String question = null; ++ ++ while (!done) { ++ int eventType = parser.next(); ++ if ((eventType == XmlPullParser.START_TAG) && ("date".equals(parser.getName()))) { ++ String dateStr = parser.nextText(); ++ long l = Long.valueOf(dateStr).longValue(); ++ date = new Date(l); ++ } ++ else if ((eventType == XmlPullParser.START_TAG) && ("duration".equals(parser.getName()))) { ++ duration = Long.valueOf(parser.nextText()).longValue(); ++ } ++ else if ((eventType == XmlPullParser.START_TAG) && ("visitorsName".equals(parser.getName()))) { ++ visitorsName = parser.nextText(); ++ } ++ else if ((eventType == XmlPullParser.START_TAG) && ("visitorsEmail".equals(parser.getName()))) { ++ visitorsEmail = parser.nextText(); ++ } ++ else if ((eventType == XmlPullParser.START_TAG) && ("sessionID".equals(parser.getName()))) { ++ sessionID = parser.nextText(); ++ } ++ else if ((eventType == XmlPullParser.START_TAG) && ("question".equals(parser.getName()))) { ++ question = parser.nextText(); ++ } ++ else if (eventType == XmlPullParser.END_TAG && "chat-session".equals(parser.getName())) { ++ done = true; ++ } ++ } ++ return new AgentChatSession(date, duration, visitorsName, visitorsEmail, sessionID, question); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/history/AgentChatSession.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/history/AgentChatSession.java +new file mode 100644 +index 0000000..5113cda +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/history/AgentChatSession.java +@@ -0,0 +1,93 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.ext.history; ++ ++import java.util.Date; ++ ++/** ++ * Represents one chat session for an agent. ++ */ ++public class AgentChatSession { ++ public Date startDate; ++ public long duration; ++ public String visitorsName; ++ public String visitorsEmail; ++ public String sessionID; ++ public String question; ++ ++ public AgentChatSession(Date date, long duration, String visitorsName, String visitorsEmail, String sessionID, String question) { ++ this.startDate = date; ++ this.duration = duration; ++ this.visitorsName = visitorsName; ++ this.visitorsEmail = visitorsEmail; ++ this.sessionID = sessionID; ++ this.question = question; ++ } ++ ++ public Date getStartDate() { ++ return startDate; ++ } ++ ++ public void setStartDate(Date startDate) { ++ this.startDate = startDate; ++ } ++ ++ public long getDuration() { ++ return duration; ++ } ++ ++ public void setDuration(long duration) { ++ this.duration = duration; ++ } ++ ++ public String getVisitorsName() { ++ return visitorsName; ++ } ++ ++ public void setVisitorsName(String visitorsName) { ++ this.visitorsName = visitorsName; ++ } ++ ++ public String getVisitorsEmail() { ++ return visitorsEmail; ++ } ++ ++ public void setVisitorsEmail(String visitorsEmail) { ++ this.visitorsEmail = visitorsEmail; ++ } ++ ++ public String getSessionID() { ++ return sessionID; ++ } ++ ++ public void setSessionID(String sessionID) { ++ this.sessionID = sessionID; ++ } ++ ++ public void setQuestion(String question){ ++ this.question = question; ++ } ++ ++ public String getQuestion(){ ++ return question; ++ } ++ ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/history/ChatMetadata.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/history/ChatMetadata.java +new file mode 100644 +index 0000000..cb36fc7 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/history/ChatMetadata.java +@@ -0,0 +1,115 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.ext.history; ++ ++import org.jivesoftware.smackx.workgroup.util.MetaDataUtils; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.util.HashMap; ++import java.util.Map; ++ ++public class ChatMetadata extends IQ { ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "chat-metadata"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; ++ ++ ++ private String sessionID; ++ ++ public String getSessionID() { ++ return sessionID; ++ } ++ ++ public void setSessionID(String sessionID) { ++ this.sessionID = sessionID; ++ } ++ ++ ++ private Map map = new HashMap(); ++ ++ public void setMetadata(Map metadata){ ++ this.map = metadata; ++ } ++ ++ public Map getMetadata(){ ++ return map; ++ } ++ ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); ++ buf.append("").append(getSessionID()).append(""); ++ buf.append(" "); ++ ++ return buf.toString(); ++ } ++ ++ /** ++ * An IQProvider for Metadata packets. ++ * ++ * @author Derek DeMoro ++ */ ++ public static class Provider implements IQProvider { ++ ++ public Provider() { ++ super(); ++ } ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ final ChatMetadata chatM = new ChatMetadata(); ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("sessionID")) { ++ chatM.setSessionID(parser.nextText()); ++ } ++ else if (parser.getName().equals("metadata")) { ++ Map map = MetaDataUtils.parseMetaData(parser); ++ chatM.setMetadata(map); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals(ELEMENT_NAME)) { ++ done = true; ++ } ++ } ++ } ++ ++ return chatM; ++ } ++ } ++} ++ ++ ++ ++ +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/macros/Macro.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/macros/Macro.java +new file mode 100644 +index 0000000..acf6196 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/macros/Macro.java +@@ -0,0 +1,68 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.ext.macros; ++ ++/** ++ * Macro datamodel. ++ */ ++public class Macro { ++ public static final int TEXT = 0; ++ public static final int URL = 1; ++ public static final int IMAGE = 2; ++ ++ ++ private String title; ++ private String description; ++ private String response; ++ private int type; ++ ++ public String getTitle() { ++ return title; ++ } ++ ++ public void setTitle(String title) { ++ this.title = title; ++ } ++ ++ public String getDescription() { ++ return description; ++ } ++ ++ public void setDescription(String description) { ++ this.description = description; ++ } ++ ++ public String getResponse() { ++ return response; ++ } ++ ++ public void setResponse(String response) { ++ this.response = response; ++ } ++ ++ public int getType() { ++ return type; ++ } ++ ++ public void setType(int type) { ++ this.type = type; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/macros/MacroGroup.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/macros/MacroGroup.java +new file mode 100644 +index 0000000..0742b3d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/macros/MacroGroup.java +@@ -0,0 +1,143 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.ext.macros; ++ ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++ ++/** ++ * MacroGroup datamodel. ++ */ ++public class MacroGroup { ++ private List macros; ++ private List macroGroups; ++ ++ ++ // Define MacroGroup ++ private String title; ++ ++ public MacroGroup() { ++ macros = new ArrayList(); ++ macroGroups = new ArrayList(); ++ } ++ ++ public void addMacro(Macro macro) { ++ macros.add(macro); ++ } ++ ++ public void removeMacro(Macro macro) { ++ macros.remove(macro); ++ } ++ ++ public Macro getMacroByTitle(String title) { ++ Collection col = Collections.unmodifiableList(macros); ++ Iterator iter = col.iterator(); ++ while (iter.hasNext()) { ++ Macro macro = (Macro)iter.next(); ++ if (macro.getTitle().equalsIgnoreCase(title)) { ++ return macro; ++ } ++ } ++ return null; ++ } ++ ++ public void addMacroGroup(MacroGroup group) { ++ macroGroups.add(group); ++ } ++ ++ public void removeMacroGroup(MacroGroup group) { ++ macroGroups.remove(group); ++ } ++ ++ public Macro getMacro(int location) { ++ return (Macro)macros.get(location); ++ } ++ ++ public MacroGroup getMacroGroupByTitle(String title) { ++ Collection col = Collections.unmodifiableList(macroGroups); ++ Iterator iter = col.iterator(); ++ while (iter.hasNext()) { ++ MacroGroup group = (MacroGroup)iter.next(); ++ if (group.getTitle().equalsIgnoreCase(title)) { ++ return group; ++ } ++ } ++ return null; ++ } ++ ++ public MacroGroup getMacroGroup(int location) { ++ return (MacroGroup)macroGroups.get(location); ++ } ++ ++ ++ public List getMacros() { ++ return macros; ++ } ++ ++ public void setMacros(List macros) { ++ this.macros = macros; ++ } ++ ++ public List getMacroGroups() { ++ return macroGroups; ++ } ++ ++ public void setMacroGroups(List macroGroups) { ++ this.macroGroups = macroGroups; ++ } ++ ++ public String getTitle() { ++ return title; ++ } ++ ++ public void setTitle(String title) { ++ this.title = title; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ buf.append("" + getTitle() + ""); ++ buf.append(""); ++ for (Macro macro : getMacros()) ++ { ++ buf.append(""); ++ buf.append("" + macro.getTitle() + ""); ++ buf.append("" + macro.getType() + ""); ++ buf.append("" + macro.getDescription() + ""); ++ buf.append("" + macro.getResponse() + ""); ++ buf.append(""); ++ } ++ buf.append(""); ++ ++ if (getMacroGroups().size() > 0) { ++ buf.append(""); ++ for (MacroGroup groups : getMacroGroups()) { ++ buf.append(groups.toXML()); ++ } ++ buf.append(""); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/macros/Macros.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/macros/Macros.java +new file mode 100644 +index 0000000..869ec57 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/macros/Macros.java +@@ -0,0 +1,198 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.ext.macros; ++ ++import java.io.StringReader; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smack.util.StringUtils; ++import org.xmlpull.v1.XmlPullParserFactory; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * Macros iq is responsible for handling global and personal macros in the a Live Assistant ++ * Workgroup. ++ */ ++public class Macros extends IQ { ++ ++ private MacroGroup rootGroup; ++ private boolean personal; ++ private MacroGroup personalMacroGroup; ++ ++ public MacroGroup getRootGroup() { ++ return rootGroup; ++ } ++ ++ public void setRootGroup(MacroGroup rootGroup) { ++ this.rootGroup = rootGroup; ++ } ++ ++ public boolean isPersonal() { ++ return personal; ++ } ++ ++ public void setPersonal(boolean personal) { ++ this.personal = personal; ++ } ++ ++ public MacroGroup getPersonalMacroGroup() { ++ return personalMacroGroup; ++ } ++ ++ public void setPersonalMacroGroup(MacroGroup personalMacroGroup) { ++ this.personalMacroGroup = personalMacroGroup; ++ } ++ ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "macros"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); ++ if (isPersonal()) { ++ buf.append("true"); ++ } ++ if (getPersonalMacroGroup() != null) { ++ buf.append(""); ++ buf.append(StringUtils.escapeForXML(getPersonalMacroGroup().toXML())); ++ buf.append(""); ++ } ++ buf.append(" "); ++ ++ return buf.toString(); ++ } ++ ++ /** ++ * An IQProvider for Macro packets. ++ * ++ * @author Derek DeMoro ++ */ ++ public static class InternalProvider implements IQProvider { ++ ++ public InternalProvider() { ++ super(); ++ } ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ Macros macroGroup = new Macros(); ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("model")) { ++ String macros = parser.nextText(); ++ MacroGroup group = parseMacroGroups(macros); ++ macroGroup.setRootGroup(group); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals(ELEMENT_NAME)) { ++ done = true; ++ } ++ } ++ } ++ ++ return macroGroup; ++ } ++ ++ public Macro parseMacro(XmlPullParser parser) throws Exception { ++ Macro macro = new Macro(); ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("title")) { ++ parser.next(); ++ macro.setTitle(parser.getText()); ++ } ++ else if (parser.getName().equals("description")) { ++ macro.setDescription(parser.nextText()); ++ } ++ else if (parser.getName().equals("response")) { ++ macro.setResponse(parser.nextText()); ++ } ++ else if (parser.getName().equals("type")) { ++ macro.setType(Integer.valueOf(parser.nextText()).intValue()); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("macro")) { ++ done = true; ++ } ++ } ++ } ++ return macro; ++ } ++ ++ public MacroGroup parseMacroGroup(XmlPullParser parser) throws Exception { ++ MacroGroup group = new MacroGroup(); ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("macrogroup")) { ++ group.addMacroGroup(parseMacroGroup(parser)); ++ } ++ if (parser.getName().equals("title")) { ++ group.setTitle(parser.nextText()); ++ } ++ if (parser.getName().equals("macro")) { ++ group.addMacro(parseMacro(parser)); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("macrogroup")) { ++ done = true; ++ } ++ } ++ } ++ return group; ++ } ++ ++ public MacroGroup parseMacroGroups(String macros) throws Exception { ++ ++ MacroGroup group = null; ++ XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); ++ parser.setInput(new StringReader(macros)); ++ int eventType = parser.getEventType(); ++ while (eventType != XmlPullParser.END_DOCUMENT) { ++ eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("macrogroup")) { ++ group = parseMacroGroup(parser); ++ } ++ } ++ } ++ return group; ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/notes/ChatNotes.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/notes/ChatNotes.java +new file mode 100644 +index 0000000..eff3c6c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/notes/ChatNotes.java +@@ -0,0 +1,155 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.ext.notes; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * IQ packet for retrieving and adding Chat Notes. ++ */ ++public class ChatNotes extends IQ { ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "chat-notes"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; ++ ++ ++ private String sessionID; ++ private String notes; ++ ++ public String getSessionID() { ++ return sessionID; ++ } ++ ++ public void setSessionID(String sessionID) { ++ this.sessionID = sessionID; ++ } ++ ++ public String getNotes() { ++ return notes; ++ } ++ ++ public void setNotes(String notes) { ++ this.notes = notes; ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); ++ buf.append("").append(getSessionID()).append(""); ++ ++ if (getNotes() != null) { ++ buf.append("").append(getNotes()).append(""); ++ } ++ buf.append(" "); ++ ++ return buf.toString(); ++ } ++ ++ /** ++ * An IQProvider for ChatNotes packets. ++ * ++ * @author Derek DeMoro ++ */ ++ public static class Provider implements IQProvider { ++ ++ public Provider() { ++ super(); ++ } ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ ChatNotes chatNotes = new ChatNotes(); ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("sessionID")) { ++ chatNotes.setSessionID(parser.nextText()); ++ } ++ else if (parser.getName().equals("text")) { ++ String note = parser.nextText(); ++ note = note.replaceAll("\\\\n", "\n"); ++ chatNotes.setNotes(note); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals(ELEMENT_NAME)) { ++ done = true; ++ } ++ } ++ } ++ ++ return chatNotes; ++ } ++ } ++ ++ /** ++ * Replaces all instances of oldString with newString in string. ++ * ++ * @param string the String to search to perform replacements on ++ * @param oldString the String that should be replaced by newString ++ * @param newString the String that will replace all instances of oldString ++ * @return a String will all instances of oldString replaced by newString ++ */ ++ public static final String replace(String string, String oldString, String newString) { ++ if (string == null) { ++ return null; ++ } ++ // If the newString is null or zero length, just return the string since there's nothing ++ // to replace. ++ if (newString == null) { ++ return string; ++ } ++ int i = 0; ++ // Make sure that oldString appears at least once before doing any processing. ++ if ((i = string.indexOf(oldString, i)) >= 0) { ++ // Use char []'s, as they are more efficient to deal with. ++ char[] string2 = string.toCharArray(); ++ char[] newString2 = newString.toCharArray(); ++ int oLength = oldString.length(); ++ StringBuilder buf = new StringBuilder(string2.length); ++ buf.append(string2, 0, i).append(newString2); ++ i += oLength; ++ int j = i; ++ // Replace all remaining instances of oldString with newString. ++ while ((i = string.indexOf(oldString, i)) > 0) { ++ buf.append(string2, j, i - j).append(newString2); ++ i += oLength; ++ j = i; ++ } ++ buf.append(string2, j, string2.length - j); ++ return buf.toString(); ++ } ++ return string; ++ } ++} ++ ++ ++ +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentInfo.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentInfo.java +new file mode 100644 +index 0000000..8b9d230 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentInfo.java +@@ -0,0 +1,132 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * IQ packet for retrieving and changing the Agent personal information. ++ */ ++public class AgentInfo extends IQ { ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "agent-info"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; ++ ++ private String jid; ++ private String name; ++ ++ /** ++ * Returns the Agent's jid. ++ * ++ * @return the Agent's jid. ++ */ ++ public String getJid() { ++ return jid; ++ } ++ ++ /** ++ * Sets the Agent's jid. ++ * ++ * @param jid the jid of the agent. ++ */ ++ public void setJid(String jid) { ++ this.jid = jid; ++ } ++ ++ /** ++ * Returns the Agent's name. The name of the agent may be different than the user's name. ++ * This property may be shown in the webchat client. ++ * ++ * @return the Agent's name. ++ */ ++ public String getName() { ++ return name; ++ } ++ ++ /** ++ * Sets the Agent's name. The name of the agent may be different than the user's name. ++ * This property may be shown in the webchat client. ++ * ++ * @param name the new name of the agent. ++ */ ++ public void setName(String name) { ++ this.name = name; ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); ++ if (jid != null) { ++ buf.append("").append(getJid()).append(""); ++ } ++ if (name != null) { ++ buf.append("").append(getName()).append(""); ++ } ++ buf.append(" "); ++ ++ return buf.toString(); ++ } ++ ++ /** ++ * An IQProvider for AgentInfo packets. ++ * ++ * @author Gaston Dombiak ++ */ ++ public static class Provider implements IQProvider { ++ ++ public Provider() { ++ super(); ++ } ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ AgentInfo answer = new AgentInfo(); ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("jid")) { ++ answer.setJid(parser.nextText()); ++ } ++ else if (parser.getName().equals("name")) { ++ answer.setName(parser.nextText()); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals(ELEMENT_NAME)) { ++ done = true; ++ } ++ } ++ } ++ ++ return answer; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentStatus.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentStatus.java +new file mode 100644 +index 0000000..9f49033 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentStatus.java +@@ -0,0 +1,266 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.text.ParseException; ++import java.text.SimpleDateFormat; ++import java.util.*; ++ ++/** ++ * Agent status packet. ++ * ++ * @author Matt Tucker ++ */ ++public class AgentStatus implements PacketExtension { ++ ++ private static final SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); ++ ++ static { ++ UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+0")); ++ } ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "agent-status"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jabber.org/protocol/workgroup"; ++ ++ private String workgroupJID; ++ private List currentChats = new ArrayList(); ++ private int maxChats = -1; ++ ++ AgentStatus() { ++ } ++ ++ public String getWorkgroupJID() { ++ return workgroupJID; ++ } ++ ++ /** ++ * Returns a collection of ChatInfo where each ChatInfo represents a Chat where this agent ++ * is participating. ++ * ++ * @return a collection of ChatInfo where each ChatInfo represents a Chat where this agent ++ * is participating. ++ */ ++ public List getCurrentChats() { ++ return Collections.unmodifiableList(currentChats); ++ } ++ ++ public int getMaxChats() { ++ return maxChats; ++ } ++ ++ public String getElementName() { ++ return ELEMENT_NAME; ++ } ++ ++ public String getNamespace() { ++ return NAMESPACE; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\""); ++ if (workgroupJID != null) { ++ buf.append(" jid=\"").append(workgroupJID).append("\""); ++ } ++ buf.append(">"); ++ if (maxChats != -1) { ++ buf.append("").append(maxChats).append(""); ++ } ++ if (!currentChats.isEmpty()) { ++ buf.append(""); ++ for (Iterator it = currentChats.iterator(); it.hasNext();) { ++ buf.append(((ChatInfo)it.next()).toXML()); ++ } ++ buf.append(""); ++ } ++ buf.append(" "); ++ ++ return buf.toString(); ++ } ++ ++ /** ++ * Represents information about a Chat where this Agent is participating. ++ * ++ * @author Gaston Dombiak ++ */ ++ public static class ChatInfo { ++ ++ private String sessionID; ++ private String userID; ++ private Date date; ++ private String email; ++ private String username; ++ private String question; ++ ++ public ChatInfo(String sessionID, String userID, Date date, String email, String username, String question) { ++ this.sessionID = sessionID; ++ this.userID = userID; ++ this.date = date; ++ this.email = email; ++ this.username = username; ++ this.question = question; ++ } ++ ++ /** ++ * Returns the sessionID associated to this chat. Each chat will have a unique sessionID ++ * that could be used for retrieving the whole transcript of the conversation. ++ * ++ * @return the sessionID associated to this chat. ++ */ ++ public String getSessionID() { ++ return sessionID; ++ } ++ ++ /** ++ * Returns the user unique identification of the user that made the initial request and ++ * for which this chat was generated. If the user joined using an anonymous connection ++ * then the userID will be the value of the ID attribute of the USER element. Otherwise, ++ * the userID will be the bare JID of the user that made the request. ++ * ++ * @return the user unique identification of the user that made the initial request. ++ */ ++ public String getUserID() { ++ return userID; ++ } ++ ++ /** ++ * Returns the date when this agent joined the chat. ++ * ++ * @return the date when this agent joined the chat. ++ */ ++ public Date getDate() { ++ return date; ++ } ++ ++ /** ++ * Returns the email address associated with the user. ++ * ++ * @return the email address associated with the user. ++ */ ++ public String getEmail() { ++ return email; ++ } ++ ++ /** ++ * Returns the username(nickname) associated with the user. ++ * ++ * @return the username associated with the user. ++ */ ++ public String getUsername() { ++ return username; ++ } ++ ++ /** ++ * Returns the question the user asked. ++ * ++ * @return the question the user asked, if any. ++ */ ++ public String getQuestion() { ++ return question; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append(""); ++ ++ return buf.toString(); ++ } ++ } ++ ++ /** ++ * Packet extension provider for AgentStatus packets. ++ */ ++ public static class Provider implements PacketExtensionProvider { ++ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ AgentStatus agentStatus = new AgentStatus(); ++ ++ agentStatus.workgroupJID = parser.getAttributeValue("", "jid"); ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ ++ if (eventType == XmlPullParser.START_TAG) { ++ if ("chat".equals(parser.getName())) { ++ agentStatus.currentChats.add(parseChatInfo(parser)); ++ } ++ else if ("max-chats".equals(parser.getName())) { ++ agentStatus.maxChats = Integer.parseInt(parser.nextText()); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG && ++ ELEMENT_NAME.equals(parser.getName())) { ++ done = true; ++ } ++ } ++ return agentStatus; ++ } ++ ++ private ChatInfo parseChatInfo(XmlPullParser parser) { ++ ++ String sessionID = parser.getAttributeValue("", "sessionID"); ++ String userID = parser.getAttributeValue("", "userID"); ++ Date date = null; ++ try { ++ date = UTC_FORMAT.parse(parser.getAttributeValue("", "startTime")); ++ } ++ catch (ParseException e) { ++ } ++ ++ String email = parser.getAttributeValue("", "email"); ++ String username = parser.getAttributeValue("", "username"); ++ String question = parser.getAttributeValue("", "question"); ++ ++ return new ChatInfo(sessionID, userID, date, email, username, question); ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentStatusRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentStatusRequest.java +new file mode 100644 +index 0000000..48549d2 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentStatusRequest.java +@@ -0,0 +1,163 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.util.Collections; ++import java.util.HashSet; ++import java.util.Iterator; ++import java.util.Set; ++ ++/** ++ * Agent status request packet. This packet is used by agents to request the list of ++ * agents in a workgroup. The response packet contains a list of packets. Presence ++ * packets from individual agents follow. ++ * ++ * @author Matt Tucker ++ */ ++public class AgentStatusRequest extends IQ { ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "agent-status-request"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jabber.org/protocol/workgroup"; ++ ++ private Set agents; ++ ++ public AgentStatusRequest() { ++ agents = new HashSet(); ++ } ++ ++ public int getAgentCount() { ++ return agents.size(); ++ } ++ ++ public Set getAgents() { ++ return Collections.unmodifiableSet(agents); ++ } ++ ++ public String getElementName() { ++ return ELEMENT_NAME; ++ } ++ ++ public String getNamespace() { ++ return NAMESPACE; ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); ++ synchronized (agents) { ++ for (Iterator i=agents.iterator(); i.hasNext(); ) { ++ Item item = (Item) i.next(); ++ buf.append(""); ++ if (item.getName() != null) { ++ buf.append(""); ++ buf.append(item.getName()); ++ buf.append(""); ++ } ++ buf.append(""); ++ } ++ } ++ buf.append(" "); ++ return buf.toString(); ++ } ++ ++ public static class Item { ++ ++ private String jid; ++ private String type; ++ private String name; ++ ++ public Item(String jid, String type, String name) { ++ this.jid = jid; ++ this.type = type; ++ this.name = name; ++ } ++ ++ public String getJID() { ++ return jid; ++ } ++ ++ public String getType() { ++ return type; ++ } ++ ++ public String getName() { ++ return name; ++ } ++ } ++ ++ /** ++ * Packet extension provider for AgentStatusRequest packets. ++ */ ++ public static class Provider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ AgentStatusRequest statusRequest = new AgentStatusRequest(); ++ ++ if (parser.getEventType() != XmlPullParser.START_TAG) { ++ throw new IllegalStateException("Parser not in proper position, or bad XML."); ++ } ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if ((eventType == XmlPullParser.START_TAG) && ("agent".equals(parser.getName()))) { ++ statusRequest.agents.add(parseAgent(parser)); ++ } ++ else if (eventType == XmlPullParser.END_TAG && ++ "agent-status-request".equals(parser.getName())) ++ { ++ done = true; ++ } ++ } ++ return statusRequest; ++ } ++ ++ private Item parseAgent(XmlPullParser parser) throws Exception { ++ ++ boolean done = false; ++ String jid = parser.getAttributeValue("", "jid"); ++ String type = parser.getAttributeValue("", "type"); ++ String name = null; ++ while (!done) { ++ int eventType = parser.next(); ++ if ((eventType == XmlPullParser.START_TAG) && ("name".equals(parser.getName()))) { ++ name = parser.nextText(); ++ } ++ else if (eventType == XmlPullParser.END_TAG && ++ "agent".equals(parser.getName())) ++ { ++ done = true; ++ } ++ } ++ return new Item(jid, type, name); ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentWorkgroups.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentWorkgroups.java +new file mode 100644 +index 0000000..292a640 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentWorkgroups.java +@@ -0,0 +1,129 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++ ++/** ++ * Represents a request for getting the jid of the workgroups where an agent can work or could ++ * represent the result of such request which will contain the list of workgroups JIDs where the ++ * agent can work. ++ * ++ * @author Gaston Dombiak ++ */ ++public class AgentWorkgroups extends IQ { ++ ++ private String agentJID; ++ private List workgroups; ++ ++ /** ++ * Creates an AgentWorkgroups request for the given agent. This IQ will be sent and an answer ++ * will be received with the jid of the workgroups where the agent can work. ++ * ++ * @param agentJID the id of the agent to get his workgroups. ++ */ ++ public AgentWorkgroups(String agentJID) { ++ this.agentJID = agentJID; ++ this.workgroups = new ArrayList(); ++ } ++ ++ /** ++ * Creates an AgentWorkgroups which will contain the JIDs of the workgroups where an agent can ++ * work. ++ * ++ * @param agentJID the id of the agent that can work in the list of workgroups. ++ * @param workgroups the list of workgroup JIDs where the agent can work. ++ */ ++ public AgentWorkgroups(String agentJID, List workgroups) { ++ this.agentJID = agentJID; ++ this.workgroups = workgroups; ++ } ++ ++ public String getAgentJID() { ++ return agentJID; ++ } ++ ++ /** ++ * Returns a list of workgroup JIDs where the agent can work. ++ * ++ * @return a list of workgroup JIDs where the agent can work. ++ */ ++ public List getWorkgroups() { ++ return Collections.unmodifiableList(workgroups); ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append(""); ++ ++ for (Iterator it=workgroups.iterator(); it.hasNext();) { ++ String workgroupJID = it.next(); ++ buf.append(""); ++ } ++ ++ buf.append(""); ++ ++ return buf.toString(); ++ } ++ ++ /** ++ * An IQProvider for AgentWorkgroups packets. ++ * ++ * @author Gaston Dombiak ++ */ ++ public static class Provider implements IQProvider { ++ ++ public Provider() { ++ super(); ++ } ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ String agentJID = parser.getAttributeValue("", "jid"); ++ List workgroups = new ArrayList(); ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("workgroup")) { ++ workgroups.add(parser.getAttributeValue("", "jid")); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("workgroups")) { ++ done = true; ++ } ++ } ++ } ++ ++ return new AgentWorkgroups(agentJID, workgroups); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/DepartQueuePacket.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/DepartQueuePacket.java +new file mode 100644 +index 0000000..620291c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/DepartQueuePacket.java +@@ -0,0 +1,75 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++ ++/** ++ * A IQ packet used to depart a workgroup queue. There are two cases for issuing a depart ++ * queue request:
        ++ *
      • The user wants to leave the queue. In this case, an instance of this class ++ * should be created without passing in a user address. ++ *
      • An administrator or the server removes wants to remove a user from the queue. ++ * In that case, the address of the user to remove from the queue should be ++ * used to create an instance of this class.
      ++ * ++ * @author loki der quaeler ++ */ ++public class DepartQueuePacket extends IQ { ++ ++ private String user; ++ ++ /** ++ * Creates a depart queue request packet to the specified workgroup. ++ * ++ * @param workgroup the workgroup to depart. ++ */ ++ public DepartQueuePacket(String workgroup) { ++ this(workgroup, null); ++ } ++ ++ /** ++ * Creates a depart queue request to the specified workgroup and for the ++ * specified user. ++ * ++ * @param workgroup the workgroup to depart. ++ * @param user the user to make depart from the queue. ++ */ ++ public DepartQueuePacket(String workgroup, String user) { ++ this.user = user; ++ ++ setTo(workgroup); ++ setType(IQ.Type.SET); ++ setFrom(user); ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder("").append(this.user).append(""); ++ } ++ else { ++ buf.append("/>"); ++ } ++ ++ return buf.toString(); ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/MetaDataProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/MetaDataProvider.java +new file mode 100644 +index 0000000..eb868b0 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/MetaDataProvider.java +@@ -0,0 +1,48 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import java.util.Map; ++ ++import org.jivesoftware.smackx.workgroup.MetaData; ++import org.jivesoftware.smackx.workgroup.util.MetaDataUtils; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++ ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * This provider parses meta data if it's not contained already in a larger extension provider. ++ * ++ * @author loki der quaeler ++ */ ++public class MetaDataProvider implements PacketExtensionProvider { ++ ++ /** ++ * PacketExtensionProvider implementation ++ */ ++ public PacketExtension parseExtension (XmlPullParser parser) ++ throws Exception { ++ Map metaData = MetaDataUtils.parseMetaData(parser); ++ ++ return new MetaData(metaData); ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/MonitorPacket.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/MonitorPacket.java +new file mode 100644 +index 0000000..0ceecae +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/MonitorPacket.java +@@ -0,0 +1,113 @@ ++/** ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++public class MonitorPacket extends IQ { ++ ++ private String sessionID; ++ ++ private boolean isMonitor; ++ ++ public boolean isMonitor() { ++ return isMonitor; ++ } ++ ++ public void setMonitor(boolean monitor) { ++ isMonitor = monitor; ++ } ++ ++ public String getSessionID() { ++ return sessionID; ++ } ++ ++ public void setSessionID(String sessionID) { ++ this.sessionID = sessionID; ++ } ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "monitor"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; ++ ++ public String getElementName() { ++ return ELEMENT_NAME; ++ } ++ ++ public String getNamespace() { ++ return NAMESPACE; ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns="); ++ buf.append('"'); ++ buf.append(NAMESPACE); ++ buf.append('"'); ++ buf.append(">"); ++ if (sessionID != null) { ++ buf.append(""); ++ } ++ buf.append(" "); ++ return buf.toString(); ++ } ++ ++ ++ /** ++ * Packet extension provider for Monitor Packets. ++ */ ++ public static class InternalProvider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ if (parser.getEventType() != XmlPullParser.START_TAG) { ++ throw new IllegalStateException("Parser not in proper position, or bad XML."); ++ } ++ ++ MonitorPacket packet = new MonitorPacket(); ++ ++ boolean done = false; ++ ++ ++ while (!done) { ++ int eventType = parser.next(); ++ if ((eventType == XmlPullParser.START_TAG) && ("isMonitor".equals(parser.getName()))) { ++ String value = parser.nextText(); ++ if ("false".equalsIgnoreCase(value)) { ++ packet.setMonitor(false); ++ } ++ else { ++ packet.setMonitor(true); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG && "monitor".equals(parser.getName())) { ++ done = true; ++ } ++ } ++ ++ return packet; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/OccupantsInfo.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/OccupantsInfo.java +new file mode 100644 +index 0000000..0f80866 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/OccupantsInfo.java +@@ -0,0 +1,173 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.text.SimpleDateFormat; ++import java.util.*; ++ ++/** ++ * Packet used for requesting information about occupants of a room or for retrieving information ++ * such information. ++ * ++ * @author Gaston Dombiak ++ */ ++public class OccupantsInfo extends IQ { ++ ++ private static final SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); ++ ++ static { ++ UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+0")); ++ } ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "occupants-info"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; ++ ++ private String roomID; ++ private final Set occupants; ++ ++ public OccupantsInfo(String roomID) { ++ this.roomID = roomID; ++ this.occupants = new HashSet(); ++ } ++ ++ public String getRoomID() { ++ return roomID; ++ } ++ ++ public int getOccupantsCount() { ++ return occupants.size(); ++ } ++ ++ public Set getOccupants() { ++ return Collections.unmodifiableSet(occupants); ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE); ++ buf.append("\" roomID=\"").append(roomID).append("\">"); ++ synchronized (occupants) { ++ for (OccupantInfo occupant : occupants) { ++ buf.append(""); ++ // Add the occupant jid ++ buf.append(""); ++ buf.append(occupant.getJID()); ++ buf.append(""); ++ // Add the occupant nickname ++ buf.append(""); ++ buf.append(occupant.getNickname()); ++ buf.append(""); ++ // Add the date when the occupant joined the room ++ buf.append(""); ++ buf.append(UTC_FORMAT.format(occupant.getJoined())); ++ buf.append(""); ++ buf.append(""); ++ } ++ } ++ buf.append(" "); ++ return buf.toString(); ++ } ++ ++ public static class OccupantInfo { ++ ++ private String jid; ++ private String nickname; ++ private Date joined; ++ ++ public OccupantInfo(String jid, String nickname, Date joined) { ++ this.jid = jid; ++ this.nickname = nickname; ++ this.joined = joined; ++ } ++ ++ public String getJID() { ++ return jid; ++ } ++ ++ public String getNickname() { ++ return nickname; ++ } ++ ++ public Date getJoined() { ++ return joined; ++ } ++ } ++ ++ /** ++ * Packet extension provider for AgentStatusRequest packets. ++ */ ++ public static class Provider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ if (parser.getEventType() != XmlPullParser.START_TAG) { ++ throw new IllegalStateException("Parser not in proper position, or bad XML."); ++ } ++ OccupantsInfo occupantsInfo = new OccupantsInfo(parser.getAttributeValue("", "roomID")); ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if ((eventType == XmlPullParser.START_TAG) && ++ ("occupant".equals(parser.getName()))) { ++ occupantsInfo.occupants.add(parseOccupantInfo(parser)); ++ } else if (eventType == XmlPullParser.END_TAG && ++ ELEMENT_NAME.equals(parser.getName())) { ++ done = true; ++ } ++ } ++ return occupantsInfo; ++ } ++ ++ private OccupantInfo parseOccupantInfo(XmlPullParser parser) throws Exception { ++ ++ boolean done = false; ++ String jid = null; ++ String nickname = null; ++ Date joined = null; ++ while (!done) { ++ int eventType = parser.next(); ++ if ((eventType == XmlPullParser.START_TAG) && ("jid".equals(parser.getName()))) { ++ jid = parser.nextText(); ++ } else if ((eventType == XmlPullParser.START_TAG) && ++ ("nickname".equals(parser.getName()))) { ++ nickname = parser.nextText(); ++ } else if ((eventType == XmlPullParser.START_TAG) && ++ ("joined".equals(parser.getName()))) { ++ joined = UTC_FORMAT.parse(parser.nextText()); ++ } else if (eventType == XmlPullParser.END_TAG && ++ "occupant".equals(parser.getName())) { ++ done = true; ++ } ++ } ++ return new OccupantInfo(jid, nickname, joined); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/OfferRequestProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/OfferRequestProvider.java +new file mode 100644 +index 0000000..112a2ce +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/OfferRequestProvider.java +@@ -0,0 +1,210 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smackx.workgroup.MetaData; ++import org.jivesoftware.smackx.workgroup.agent.InvitationRequest; ++import org.jivesoftware.smackx.workgroup.agent.OfferContent; ++import org.jivesoftware.smackx.workgroup.agent.TransferRequest; ++import org.jivesoftware.smackx.workgroup.agent.UserRequest; ++import org.jivesoftware.smackx.workgroup.util.MetaDataUtils; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smack.util.PacketParserUtils; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.util.HashMap; ++import java.util.Map; ++ ++/** ++ * An IQProvider for agent offer requests. ++ * ++ * @author loki der quaeler ++ */ ++public class OfferRequestProvider implements IQProvider { ++ ++ public OfferRequestProvider() { ++ } ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ int eventType = parser.getEventType(); ++ String sessionID = null; ++ int timeout = -1; ++ OfferContent content = null; ++ boolean done = false; ++ Map metaData = new HashMap(); ++ ++ if (eventType != XmlPullParser.START_TAG) { ++ // throw exception ++ } ++ ++ String userJID = parser.getAttributeValue("", "jid"); ++ // Default userID to the JID. ++ String userID = userJID; ++ ++ while (!done) { ++ eventType = parser.next(); ++ ++ if (eventType == XmlPullParser.START_TAG) { ++ String elemName = parser.getName(); ++ ++ if ("timeout".equals(elemName)) { ++ timeout = Integer.parseInt(parser.nextText()); ++ } ++ else if (MetaData.ELEMENT_NAME.equals(elemName)) { ++ metaData = MetaDataUtils.parseMetaData(parser); ++ } ++ else if (SessionID.ELEMENT_NAME.equals(elemName)) { ++ sessionID = parser.getAttributeValue("", "id"); ++ } ++ else if (UserID.ELEMENT_NAME.equals(elemName)) { ++ userID = parser.getAttributeValue("", "id"); ++ } ++ else if ("user-request".equals(elemName)) { ++ content = UserRequest.getInstance(); ++ } ++ else if (RoomInvitation.ELEMENT_NAME.equals(elemName)) { ++ RoomInvitation invitation = (RoomInvitation) PacketParserUtils ++ .parsePacketExtension(RoomInvitation.ELEMENT_NAME, RoomInvitation.NAMESPACE, parser); ++ content = new InvitationRequest(invitation.getInviter(), invitation.getRoom(), ++ invitation.getReason()); ++ } ++ else if (RoomTransfer.ELEMENT_NAME.equals(elemName)) { ++ RoomTransfer transfer = (RoomTransfer) PacketParserUtils ++ .parsePacketExtension(RoomTransfer.ELEMENT_NAME, RoomTransfer.NAMESPACE, parser); ++ content = new TransferRequest(transfer.getInviter(), transfer.getRoom(), transfer.getReason()); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if ("offer".equals(parser.getName())) { ++ done = true; ++ } ++ } ++ } ++ ++ OfferRequestPacket offerRequest = ++ new OfferRequestPacket(userJID, userID, timeout, metaData, sessionID, content); ++ offerRequest.setType(IQ.Type.SET); ++ ++ return offerRequest; ++ } ++ ++ public static class OfferRequestPacket extends IQ { ++ ++ private int timeout; ++ private String userID; ++ private String userJID; ++ private Map metaData; ++ private String sessionID; ++ private OfferContent content; ++ ++ public OfferRequestPacket(String userJID, String userID, int timeout, Map metaData, ++ String sessionID, OfferContent content) ++ { ++ this.userJID = userJID; ++ this.userID = userID; ++ this.timeout = timeout; ++ this.metaData = metaData; ++ this.sessionID = sessionID; ++ this.content = content; ++ } ++ ++ /** ++ * Returns the userID, which is either the same as the userJID or a special ++ * value that the user provided as part of their "join queue" request. ++ * ++ * @return the user ID. ++ */ ++ public String getUserID() { ++ return userID; ++ } ++ ++ /** ++ * The JID of the user that made the "join queue" request. ++ * ++ * @return the user JID. ++ */ ++ public String getUserJID() { ++ return userJID; ++ } ++ ++ /** ++ * Returns the session ID associated with the request and ensuing chat. If the offer ++ * does not contain a session ID, null will be returned. ++ * ++ * @return the session id associated with the request. ++ */ ++ public String getSessionID() { ++ return sessionID; ++ } ++ ++ /** ++ * Returns the number of seconds the agent has to accept the offer before ++ * it times out. ++ * ++ * @return the offer timeout (in seconds). ++ */ ++ public int getTimeout() { ++ return this.timeout; ++ } ++ ++ public OfferContent getContent() { ++ return content; ++ } ++ ++ /** ++ * Returns any meta-data associated with the offer. ++ * ++ * @return meta-data associated with the offer. ++ */ ++ public Map getMetaData() { ++ return this.metaData; ++ } ++ ++ public String getChildElementXML () { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append(""); ++ buf.append("").append(timeout).append(""); ++ ++ if (sessionID != null) { ++ buf.append('<').append(SessionID.ELEMENT_NAME); ++ buf.append(" session=\""); ++ buf.append(getSessionID()).append("\" xmlns=\""); ++ buf.append(SessionID.NAMESPACE).append("\"/>"); ++ } ++ ++ if (metaData != null) { ++ buf.append(MetaDataUtils.serializeMetaData(metaData)); ++ } ++ ++ if (userID != null) { ++ buf.append('<').append(UserID.ELEMENT_NAME); ++ buf.append(" id=\""); ++ buf.append(userID).append("\" xmlns=\""); ++ buf.append(UserID.NAMESPACE).append("\"/>"); ++ } ++ ++ buf.append(""); ++ ++ return buf.toString(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/OfferRevokeProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/OfferRevokeProvider.java +new file mode 100644 +index 0000000..202824c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/OfferRevokeProvider.java +@@ -0,0 +1,112 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * An IQProvider class which has savvy about the offer-revoke tag.
      ++ * ++ * @author loki der quaeler ++ */ ++public class OfferRevokeProvider implements IQProvider { ++ ++ public IQ parseIQ (XmlPullParser parser) throws Exception { ++ // The parser will be positioned on the opening IQ tag, so get the JID attribute. ++ String userJID = parser.getAttributeValue("", "jid"); ++ // Default the userID to the JID. ++ String userID = userJID; ++ String reason = null; ++ String sessionID = null; ++ boolean done = false; ++ ++ while (!done) { ++ int eventType = parser.next(); ++ ++ if ((eventType == XmlPullParser.START_TAG) && parser.getName().equals("reason")) { ++ reason = parser.nextText(); ++ } ++ else if ((eventType == XmlPullParser.START_TAG) ++ && parser.getName().equals(SessionID.ELEMENT_NAME)) { ++ sessionID = parser.getAttributeValue("", "id"); ++ } ++ else if ((eventType == XmlPullParser.START_TAG) ++ && parser.getName().equals(UserID.ELEMENT_NAME)) { ++ userID = parser.getAttributeValue("", "id"); ++ } ++ else if ((eventType == XmlPullParser.END_TAG) && parser.getName().equals( ++ "offer-revoke")) ++ { ++ done = true; ++ } ++ } ++ ++ return new OfferRevokePacket(userJID, userID, reason, sessionID); ++ } ++ ++ public class OfferRevokePacket extends IQ { ++ ++ private String userJID; ++ private String userID; ++ private String sessionID; ++ private String reason; ++ ++ public OfferRevokePacket (String userJID, String userID, String cause, String sessionID) { ++ this.userJID = userJID; ++ this.userID = userID; ++ this.reason = cause; ++ this.sessionID = sessionID; ++ } ++ ++ public String getUserJID() { ++ return userJID; ++ } ++ ++ public String getUserID() { ++ return this.userID; ++ } ++ ++ public String getReason() { ++ return this.reason; ++ } ++ ++ public String getSessionID() { ++ return this.sessionID; ++ } ++ ++ public String getChildElementXML () { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ if (reason != null) { ++ buf.append("").append(reason).append(""); ++ } ++ if (sessionID != null) { ++ buf.append(new SessionID(sessionID).toXML()); ++ } ++ if (userID != null) { ++ buf.append(new UserID(userID).toXML()); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java +new file mode 100644 +index 0000000..ef11a78 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java +@@ -0,0 +1,199 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smackx.workgroup.QueueUser; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.text.SimpleDateFormat; ++import java.util.Date; ++import java.util.HashSet; ++import java.util.Iterator; ++import java.util.Set; ++ ++/** ++ * Queue details packet extension, which contains details about the users ++ * currently in a queue. ++ */ ++public class QueueDetails implements PacketExtension { ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "notify-queue-details"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jabber.org/protocol/workgroup"; ++ ++ private static final String DATE_FORMAT = "yyyyMMdd'T'HH:mm:ss"; ++ ++ private SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); ++ /** ++ * The list of users in the queue. ++ */ ++ private Set users; ++ ++ /** ++ * Creates a new QueueDetails packet ++ */ ++ private QueueDetails() { ++ users = new HashSet(); ++ } ++ ++ /** ++ * Returns the number of users currently in the queue that are waiting to ++ * be routed to an agent. ++ * ++ * @return the number of users in the queue. ++ */ ++ public int getUserCount() { ++ return users.size(); ++ } ++ ++ /** ++ * Returns the set of users in the queue that are waiting to ++ * be routed to an agent (as QueueUser objects). ++ * ++ * @return a Set for the users waiting in a queue. ++ */ ++ public Set getUsers() { ++ synchronized (users) { ++ return users; ++ } ++ } ++ ++ /** ++ * Adds a user to the packet. ++ * ++ * @param user the user. ++ */ ++ private void addUser(QueueUser user) { ++ synchronized (users) { ++ users.add(user); ++ } ++ } ++ ++ public String getElementName() { ++ return ELEMENT_NAME; ++ } ++ ++ public String getNamespace() { ++ return NAMESPACE; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); ++ ++ synchronized (users) { ++ for (Iterator i=users.iterator(); i.hasNext(); ) { ++ QueueUser user = (QueueUser)i.next(); ++ int position = user.getQueuePosition(); ++ int timeRemaining = user.getEstimatedRemainingTime(); ++ Date timestamp = user.getQueueJoinTimestamp(); ++ ++ buf.append(""); ++ ++ if (position != -1) { ++ buf.append("").append(position).append(""); ++ } ++ ++ if (timeRemaining != -1) { ++ buf.append(""); ++ } ++ ++ if (timestamp != null) { ++ buf.append(""); ++ buf.append(dateFormat.format(timestamp)); ++ buf.append(""); ++ } ++ ++ buf.append(""); ++ } ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ /** ++ * Provider class for QueueDetails packet extensions. ++ */ ++ public static class Provider implements PacketExtensionProvider { ++ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ ++ SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); ++ QueueDetails queueDetails = new QueueDetails(); ++ ++ int eventType = parser.getEventType(); ++ while (eventType != XmlPullParser.END_TAG && ++ "notify-queue-details".equals(parser.getName())) ++ { ++ eventType = parser.next(); ++ while ((eventType == XmlPullParser.START_TAG) && "user".equals(parser.getName())) { ++ String uid = null; ++ int position = -1; ++ int time = -1; ++ Date joinTime = null; ++ ++ uid = parser.getAttributeValue("", "jid"); ++ ++ if (uid == null) { ++ // throw exception ++ } ++ ++ eventType = parser.next(); ++ while ((eventType != XmlPullParser.END_TAG) ++ || (! "user".equals(parser.getName()))) ++ { ++ if ("position".equals(parser.getName())) { ++ position = Integer.parseInt(parser.nextText()); ++ } ++ else if ("time".equals(parser.getName())) { ++ time = Integer.parseInt(parser.nextText()); ++ } ++ else if ("join-time".equals(parser.getName())) { ++ joinTime = dateFormat.parse(parser.nextText()); ++ } ++ else if( parser.getName().equals( "waitTime" ) ) { ++ Date wait = dateFormat.parse(parser.nextText()); ++ System.out.println( wait ); ++ } ++ ++ eventType = parser.next(); ++ ++ if (eventType != XmlPullParser.END_TAG) { ++ // throw exception ++ } ++ } ++ ++ queueDetails.addUser(new QueueUser(uid, position, time, joinTime)); ++ ++ eventType = parser.next(); ++ } ++ } ++ return queueDetails; ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/QueueOverview.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/QueueOverview.java +new file mode 100644 +index 0000000..a559579 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/QueueOverview.java +@@ -0,0 +1,160 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smackx.workgroup.agent.WorkgroupQueue; ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.text.SimpleDateFormat; ++import java.util.Date; ++ ++public class QueueOverview implements PacketExtension { ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static String ELEMENT_NAME = "notify-queue"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static String NAMESPACE = "http://jabber.org/protocol/workgroup"; ++ ++ private static final String DATE_FORMAT = "yyyyMMdd'T'HH:mm:ss"; ++ private SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); ++ ++ private int averageWaitTime; ++ private Date oldestEntry; ++ private int userCount; ++ private WorkgroupQueue.Status status; ++ ++ QueueOverview() { ++ this.averageWaitTime = -1; ++ this.oldestEntry = null; ++ this.userCount = -1; ++ this.status = null; ++ } ++ ++ void setAverageWaitTime(int averageWaitTime) { ++ this.averageWaitTime = averageWaitTime; ++ } ++ ++ public int getAverageWaitTime () { ++ return averageWaitTime; ++ } ++ ++ void setOldestEntry(Date oldestEntry) { ++ this.oldestEntry = oldestEntry; ++ } ++ ++ public Date getOldestEntry() { ++ return oldestEntry; ++ } ++ ++ void setUserCount(int userCount) { ++ this.userCount = userCount; ++ } ++ ++ public int getUserCount() { ++ return userCount; ++ } ++ ++ public WorkgroupQueue.Status getStatus() { ++ return status; ++ } ++ ++ void setStatus(WorkgroupQueue.Status status) { ++ this.status = status; ++ } ++ ++ public String getElementName () { ++ return ELEMENT_NAME; ++ } ++ ++ public String getNamespace () { ++ return NAMESPACE; ++ } ++ ++ public String toXML () { ++ StringBuilder buf = new StringBuilder(); ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); ++ ++ if (userCount != -1) { ++ buf.append("").append(userCount).append(""); ++ } ++ if (oldestEntry != null) { ++ buf.append("").append(dateFormat.format(oldestEntry)).append(""); ++ } ++ if (averageWaitTime != -1) { ++ buf.append(""); ++ } ++ if (status != null) { ++ buf.append("").append(status).append(""); ++ } ++ buf.append(""); ++ ++ return buf.toString(); ++ } ++ ++ public static class Provider implements PacketExtensionProvider { ++ ++ public PacketExtension parseExtension (XmlPullParser parser) throws Exception { ++ int eventType = parser.getEventType(); ++ QueueOverview queueOverview = new QueueOverview(); ++ SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); ++ ++ if (eventType != XmlPullParser.START_TAG) { ++ // throw exception ++ } ++ ++ eventType = parser.next(); ++ while ((eventType != XmlPullParser.END_TAG) ++ || (!ELEMENT_NAME.equals(parser.getName()))) ++ { ++ if ("count".equals(parser.getName())) { ++ queueOverview.setUserCount(Integer.parseInt(parser.nextText())); ++ } ++ else if ("time".equals(parser.getName())) { ++ queueOverview.setAverageWaitTime(Integer.parseInt(parser.nextText())); ++ } ++ else if ("oldest".equals(parser.getName())) { ++ queueOverview.setOldestEntry((dateFormat.parse(parser.nextText()))); ++ } ++ else if ("status".equals(parser.getName())) { ++ queueOverview.setStatus(WorkgroupQueue.Status.fromString(parser.nextText())); ++ } ++ ++ eventType = parser.next(); ++ ++ if (eventType != XmlPullParser.END_TAG) { ++ // throw exception ++ } ++ } ++ ++ if (eventType != XmlPullParser.END_TAG) { ++ // throw exception ++ } ++ ++ return queueOverview; ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/QueueUpdate.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/QueueUpdate.java +new file mode 100644 +index 0000000..c326a57 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/QueueUpdate.java +@@ -0,0 +1,122 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * An IQ packet that encapsulates both types of workgroup queue ++ * status notifications -- position updates, and estimated time ++ * left in the queue updates. ++ */ ++public class QueueUpdate implements PacketExtension { ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "queue-status"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jabber.org/protocol/workgroup"; ++ ++ private int position; ++ private int remainingTime; ++ ++ public QueueUpdate(int position, int remainingTime) { ++ this.position = position; ++ this.remainingTime = remainingTime; ++ } ++ ++ /** ++ * Returns the user's position in the workgroup queue, or -1 if the ++ * value isn't set on this packet. ++ * ++ * @return the position in the workgroup queue. ++ */ ++ public int getPosition() { ++ return this.position; ++ } ++ ++ /** ++ * Returns the user's estimated time left in the workgroup queue, or ++ * -1 if the value isn't set on this packet. ++ * ++ * @return the estimated time left in the workgroup queue. ++ */ ++ public int getRemaingTime() { ++ return remainingTime; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ buf.append(""); ++ if (position != -1) { ++ buf.append("").append(position).append(""); ++ } ++ if (remainingTime != -1) { ++ buf.append(""); ++ } ++ buf.append(""); ++ return buf.toString(); ++ } ++ ++ public String getElementName() { ++ return ELEMENT_NAME; ++ } ++ ++ public String getNamespace() { ++ return NAMESPACE; ++ } ++ ++ public static class Provider implements PacketExtensionProvider { ++ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ boolean done = false; ++ int position = -1; ++ int timeRemaining = -1; ++ while (!done) { ++ parser.next(); ++ String elementName = parser.getName(); ++ if (parser.getEventType() == XmlPullParser.START_TAG && "position".equals(elementName)) { ++ try { ++ position = Integer.parseInt(parser.nextText()); ++ } ++ catch (NumberFormatException nfe) { ++ } ++ } ++ else if (parser.getEventType() == XmlPullParser.START_TAG && "time".equals(elementName)) { ++ try { ++ timeRemaining = Integer.parseInt(parser.nextText()); ++ } ++ catch (NumberFormatException nfe) { ++ } ++ } ++ else if (parser.getEventType() == XmlPullParser.END_TAG && "queue-status".equals(elementName)) { ++ done = true; ++ } ++ } ++ return new QueueUpdate(position, timeRemaining); ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java +new file mode 100644 +index 0000000..34555de +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java +@@ -0,0 +1,177 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * Packet extension for {@link org.jivesoftware.smackx.workgroup.agent.InvitationRequest}. ++ * ++ * @author Gaston Dombiak ++ */ ++public class RoomInvitation implements PacketExtension { ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "invite"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jabber.org/protocol/workgroup"; ++ ++ /** ++ * Type of entity being invited to a groupchat support session. ++ */ ++ private Type type; ++ /** ++ * JID of the entity being invited. The entity could be another agent, user , a queue or a workgroup. In ++ * the case of a queue or a workgroup the server will select the best agent to invite. ++ */ ++ private String invitee; ++ /** ++ * Full JID of the user that sent the invitation. ++ */ ++ private String inviter; ++ /** ++ * ID of the session that originated the initial user request. ++ */ ++ private String sessionID; ++ /** ++ * JID of the room to join if offer is accepted. ++ */ ++ private String room; ++ /** ++ * Text provided by the inviter explaining the reason why the invitee is invited. ++ */ ++ private String reason; ++ ++ public RoomInvitation(Type type, String invitee, String sessionID, String reason) { ++ this.type = type; ++ this.invitee = invitee; ++ this.sessionID = sessionID; ++ this.reason = reason; ++ } ++ ++ private RoomInvitation() { ++ } ++ ++ public String getElementName() { ++ return ELEMENT_NAME; ++ } ++ ++ public String getNamespace() { ++ return NAMESPACE; ++ } ++ ++ public String getInviter() { ++ return inviter; ++ } ++ ++ public String getRoom() { ++ return room; ++ } ++ ++ public String getReason() { ++ return reason; ++ } ++ ++ public String getSessionID() { ++ return sessionID; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE); ++ buf.append("\" type=\"").append(type).append("\">"); ++ buf.append(""); ++ if (invitee != null) { ++ buf.append("").append(invitee).append(""); ++ } ++ if (inviter != null) { ++ buf.append("").append(inviter).append(""); ++ } ++ if (reason != null) { ++ buf.append("").append(reason).append(""); ++ } ++ // Add packet extensions, if any are defined. ++ buf.append(" "); ++ ++ return buf.toString(); ++ } ++ ++ /** ++ * Type of entity being invited to a groupchat support session. ++ */ ++ public static enum Type { ++ /** ++ * A user is being invited to a groupchat support session. The user could be another agent ++ * or just a regular XMPP user. ++ */ ++ user, ++ /** ++ * Some agent of the specified queue will be invited to the groupchat support session. ++ */ ++ queue, ++ /** ++ * Some agent of the specified workgroup will be invited to the groupchat support session. ++ */ ++ workgroup ++ } ++ ++ public static class Provider implements PacketExtensionProvider { ++ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ final RoomInvitation invitation = new RoomInvitation(); ++ invitation.type = Type.valueOf(parser.getAttributeValue("", "type")); ++ ++ boolean done = false; ++ while (!done) { ++ parser.next(); ++ String elementName = parser.getName(); ++ if (parser.getEventType() == XmlPullParser.START_TAG) { ++ if ("session".equals(elementName)) { ++ invitation.sessionID = parser.getAttributeValue("", "id"); ++ } ++ else if ("invitee".equals(elementName)) { ++ invitation.invitee = parser.nextText(); ++ } ++ else if ("inviter".equals(elementName)) { ++ invitation.inviter = parser.nextText(); ++ } ++ else if ("reason".equals(elementName)) { ++ invitation.reason = parser.nextText(); ++ } ++ else if ("room".equals(elementName)) { ++ invitation.room = parser.nextText(); ++ } ++ } ++ else if (parser.getEventType() == XmlPullParser.END_TAG && ELEMENT_NAME.equals(elementName)) { ++ done = true; ++ } ++ } ++ return invitation; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java +new file mode 100644 +index 0000000..d1e83e2 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java +@@ -0,0 +1,177 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * Packet extension for {@link org.jivesoftware.smackx.workgroup.agent.TransferRequest}. ++ * ++ * @author Gaston Dombiak ++ */ ++public class RoomTransfer implements PacketExtension { ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "transfer"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jabber.org/protocol/workgroup"; ++ ++ /** ++ * Type of entity being invited to a groupchat support session. ++ */ ++ private RoomTransfer.Type type; ++ /** ++ * JID of the entity being invited. The entity could be another agent, user , a queue or a workgroup. In ++ * the case of a queue or a workgroup the server will select the best agent to invite. ++ */ ++ private String invitee; ++ /** ++ * Full JID of the user that sent the invitation. ++ */ ++ private String inviter; ++ /** ++ * ID of the session that originated the initial user request. ++ */ ++ private String sessionID; ++ /** ++ * JID of the room to join if offer is accepted. ++ */ ++ private String room; ++ /** ++ * Text provided by the inviter explaining the reason why the invitee is invited. ++ */ ++ private String reason; ++ ++ public RoomTransfer(RoomTransfer.Type type, String invitee, String sessionID, String reason) { ++ this.type = type; ++ this.invitee = invitee; ++ this.sessionID = sessionID; ++ this.reason = reason; ++ } ++ ++ private RoomTransfer() { ++ } ++ ++ public String getElementName() { ++ return ELEMENT_NAME; ++ } ++ ++ public String getNamespace() { ++ return NAMESPACE; ++ } ++ ++ public String getInviter() { ++ return inviter; ++ } ++ ++ public String getRoom() { ++ return room; ++ } ++ ++ public String getReason() { ++ return reason; ++ } ++ ++ public String getSessionID() { ++ return sessionID; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE); ++ buf.append("\" type=\"").append(type).append("\">"); ++ buf.append(""); ++ if (invitee != null) { ++ buf.append("").append(invitee).append(""); ++ } ++ if (inviter != null) { ++ buf.append("").append(inviter).append(""); ++ } ++ if (reason != null) { ++ buf.append("").append(reason).append(""); ++ } ++ // Add packet extensions, if any are defined. ++ buf.append(" "); ++ ++ return buf.toString(); ++ } ++ ++ /** ++ * Type of entity being invited to a groupchat support session. ++ */ ++ public static enum Type { ++ /** ++ * A user is being invited to a groupchat support session. The user could be another agent ++ * or just a regular XMPP user. ++ */ ++ user, ++ /** ++ * Some agent of the specified queue will be invited to the groupchat support session. ++ */ ++ queue, ++ /** ++ * Some agent of the specified workgroup will be invited to the groupchat support session. ++ */ ++ workgroup ++ } ++ ++ public static class Provider implements PacketExtensionProvider { ++ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ final RoomTransfer invitation = new RoomTransfer(); ++ invitation.type = RoomTransfer.Type.valueOf(parser.getAttributeValue("", "type")); ++ ++ boolean done = false; ++ while (!done) { ++ parser.next(); ++ String elementName = parser.getName(); ++ if (parser.getEventType() == XmlPullParser.START_TAG) { ++ if ("session".equals(elementName)) { ++ invitation.sessionID = parser.getAttributeValue("", "id"); ++ } ++ else if ("invitee".equals(elementName)) { ++ invitation.invitee = parser.nextText(); ++ } ++ else if ("inviter".equals(elementName)) { ++ invitation.inviter = parser.nextText(); ++ } ++ else if ("reason".equals(elementName)) { ++ invitation.reason = parser.nextText(); ++ } ++ else if ("room".equals(elementName)) { ++ invitation.room = parser.nextText(); ++ } ++ } ++ else if (parser.getEventType() == XmlPullParser.END_TAG && ELEMENT_NAME.equals(elementName)) { ++ done = true; ++ } ++ } ++ return invitation; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/SessionID.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/SessionID.java +new file mode 100644 +index 0000000..bfd7cfd +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/SessionID.java +@@ -0,0 +1,77 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++public class SessionID implements PacketExtension { ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "session"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; ++ ++ private String sessionID; ++ ++ public SessionID(String sessionID) { ++ this.sessionID = sessionID; ++ } ++ ++ public String getSessionID() { ++ return this.sessionID; ++ } ++ ++ public String getElementName() { ++ return ELEMENT_NAME; ++ } ++ ++ public String getNamespace() { ++ return NAMESPACE; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\" "); ++ buf.append("id=\"").append(this.getSessionID()); ++ buf.append("\"/>"); ++ ++ return buf.toString(); ++ } ++ ++ public static class Provider implements PacketExtensionProvider { ++ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ String sessionID = parser.getAttributeValue("", "id"); ++ ++ // Advance to end of extension. ++ parser.next(); ++ ++ return new SessionID(sessionID); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/Transcript.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/Transcript.java +new file mode 100644 +index 0000000..7f8f29e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/Transcript.java +@@ -0,0 +1,98 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++ ++/** ++ * Represents the conversation transcript that occured in a group chat room between an Agent ++ * and a user that requested assistance. The transcript contains all the Messages that were sent ++ * to the room as well as the sent presences. ++ * ++ * @author Gaston Dombiak ++ */ ++public class Transcript extends IQ { ++ private String sessionID; ++ private List packets; ++ ++ /** ++ * Creates a transcript request for the given sessionID. ++ * ++ * @param sessionID the id of the session to get the conversation transcript. ++ */ ++ public Transcript(String sessionID) { ++ this.sessionID = sessionID; ++ this.packets = new ArrayList(); ++ } ++ ++ /** ++ * Creates a new transcript for the given sessionID and list of packets. The list of packets ++ * may include Messages and/or Presences. ++ * ++ * @param sessionID the id of the session that generated this conversation transcript. ++ * @param packets the list of messages and presences send to the room. ++ */ ++ public Transcript(String sessionID, List packets) { ++ this.sessionID = sessionID; ++ this.packets = packets; ++ } ++ ++ /** ++ * Returns id of the session that generated this conversation transcript. The sessionID is a ++ * value generated by the server when a new request is received. ++ * ++ * @return id of the session that generated this conversation transcript. ++ */ ++ public String getSessionID() { ++ return sessionID; ++ } ++ ++ /** ++ * Returns the list of Messages and Presences that were sent to the room. ++ * ++ * @return the list of Messages and Presences that were sent to the room. ++ */ ++ public List getPackets() { ++ return Collections.unmodifiableList(packets); ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append(""); ++ ++ for (Iterator it=packets.iterator(); it.hasNext();) { ++ Packet packet = it.next(); ++ buf.append(packet.toXML()); ++ } ++ ++ buf.append(""); ++ ++ return buf.toString(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/TranscriptProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/TranscriptProvider.java +new file mode 100644 +index 0000000..791b06e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/TranscriptProvider.java +@@ -0,0 +1,66 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.packet.Packet; ++import org.jivesoftware.smack.util.PacketParserUtils; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++/** ++ * An IQProvider for transcripts. ++ * ++ * @author Gaston Dombiak ++ */ ++public class TranscriptProvider implements IQProvider { ++ ++ public TranscriptProvider() { ++ super(); ++ } ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ String sessionID = parser.getAttributeValue("", "sessionID"); ++ List packets = new ArrayList(); ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("message")) { ++ packets.add(PacketParserUtils.parseMessage(parser)); ++ } ++ else if (parser.getName().equals("presence")) { ++ packets.add(PacketParserUtils.parsePresence(parser)); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("transcript")) { ++ done = true; ++ } ++ } ++ } ++ ++ return new Transcript(sessionID, packets); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/TranscriptSearch.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/TranscriptSearch.java +new file mode 100644 +index 0000000..72693c4 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/TranscriptSearch.java +@@ -0,0 +1,87 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smack.util.PacketParserUtils; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * IQ packet for retrieving the transcript search form, submiting the completed search form ++ * or retrieving the answer of a transcript search. ++ * ++ * @author Gaston Dombiak ++ */ ++public class TranscriptSearch extends IQ { ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "transcript-search"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); ++ // Add packet extensions, if any are defined. ++ buf.append(getExtensionsXML()); ++ buf.append(" "); ++ ++ return buf.toString(); ++ } ++ ++ /** ++ * An IQProvider for TranscriptSearch packets. ++ * ++ * @author Gaston Dombiak ++ */ ++ public static class Provider implements IQProvider { ++ ++ public Provider() { ++ super(); ++ } ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ TranscriptSearch answer = new TranscriptSearch(); ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ // Parse the packet extension ++ answer.addExtension(PacketParserUtils.parsePacketExtension(parser.getName(), parser.getNamespace(), parser)); ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals(ELEMENT_NAME)) { ++ done = true; ++ } ++ } ++ } ++ ++ return answer; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/Transcripts.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/Transcripts.java +new file mode 100644 +index 0000000..66ddaad +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/Transcripts.java +@@ -0,0 +1,247 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++ ++import java.text.SimpleDateFormat; ++import java.util.*; ++ ++/** ++ * Represents a list of conversation transcripts that a user had in all his history. Each ++ * transcript summary includes the sessionID which may be used for getting more detailed ++ * information about the conversation. {@link org.jivesoftware.smackx.workgroup.packet.Transcript} ++ * ++ * @author Gaston Dombiak ++ */ ++public class Transcripts extends IQ { ++ ++ private static final SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); ++ static { ++ UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+0")); ++ } ++ ++ private String userID; ++ private List summaries; ++ ++ ++ /** ++ * Creates a transcripts request for the given userID. ++ * ++ * @param userID the id of the user to get his conversations transcripts. ++ */ ++ public Transcripts(String userID) { ++ this.userID = userID; ++ this.summaries = new ArrayList(); ++ } ++ ++ /** ++ * Creates a Transcripts which will contain the transcript summaries of the given user. ++ * ++ * @param userID the id of the user. Could be a real JID or a unique String that identifies ++ * anonymous users. ++ * @param summaries the list of TranscriptSummaries. ++ */ ++ public Transcripts(String userID, List summaries) { ++ this.userID = userID; ++ this.summaries = summaries; ++ } ++ ++ /** ++ * Returns the id of the user that was involved in the conversations. The userID could be a ++ * real JID if the connected user was not anonymous. Otherwise, the userID will be a String ++ * that was provided by the anonymous user as a way to idenitify the user across many user ++ * sessions. ++ * ++ * @return the id of the user that was involved in the conversations. ++ */ ++ public String getUserID() { ++ return userID; ++ } ++ ++ /** ++ * Returns a list of TranscriptSummary. A TranscriptSummary does not contain the conversation ++ * transcript but some summary information like the sessionID and the time when the ++ * conversation started and finished. Once you have the sessionID it is possible to get the ++ * full conversation transcript. ++ * ++ * @return a list of TranscriptSummary. ++ */ ++ public List getSummaries() { ++ return Collections.unmodifiableList(summaries); ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append(""); ++ ++ for (TranscriptSummary transcriptSummary : summaries) { ++ buf.append(transcriptSummary.toXML()); ++ } ++ ++ buf.append(""); ++ ++ return buf.toString(); ++ } ++ ++ /** ++ * A TranscriptSummary contains some information about a conversation such as the ID of the ++ * session or the date when the conversation started and finished. You will need to use the ++ * sessionID to get the full conversation transcript. ++ */ ++ public static class TranscriptSummary { ++ private String sessionID; ++ private Date joinTime; ++ private Date leftTime; ++ private List agentDetails; ++ ++ public TranscriptSummary(String sessionID, Date joinTime, Date leftTime, List agentDetails) { ++ this.sessionID = sessionID; ++ this.joinTime = joinTime; ++ this.leftTime = leftTime; ++ this.agentDetails = agentDetails; ++ } ++ ++ /** ++ * Returns the ID of the session that is related to this conversation transcript. The ++ * sessionID could be used for getting the full conversation transcript. ++ * ++ * @return the ID of the session that is related to this conversation transcript. ++ */ ++ public String getSessionID() { ++ return sessionID; ++ } ++ ++ /** ++ * Returns the Date when the conversation started. ++ * ++ * @return the Date when the conversation started. ++ */ ++ public Date getJoinTime() { ++ return joinTime; ++ } ++ ++ /** ++ * Returns the Date when the conversation finished. ++ * ++ * @return the Date when the conversation finished. ++ */ ++ public Date getLeftTime() { ++ return leftTime; ++ } ++ ++ /** ++ * Returns a list of AgentDetails. For each Agent that was involved in the conversation ++ * the list will include an AgentDetail. An AgentDetail contains the JID of the agent ++ * as well as the time when the Agent joined and left the conversation. ++ * ++ * @return a list of AgentDetails. ++ */ ++ public List getAgentDetails() { ++ return agentDetails; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append(""); ++ ++ if (joinTime != null) { ++ buf.append("").append(UTC_FORMAT.format(joinTime)).append(""); ++ } ++ if (leftTime != null) { ++ buf.append("").append(UTC_FORMAT.format(leftTime)).append(""); ++ } ++ buf.append(""); ++ for (AgentDetail agentDetail : agentDetails) { ++ buf.append(agentDetail.toXML()); ++ } ++ buf.append(""); ++ ++ return buf.toString(); ++ } ++ } ++ ++ /** ++ * An AgentDetail contains information of an Agent that was involved in a conversation. ++ */ ++ public static class AgentDetail { ++ private String agentJID; ++ private Date joinTime; ++ private Date leftTime; ++ ++ public AgentDetail(String agentJID, Date joinTime, Date leftTime) { ++ this.agentJID = agentJID; ++ this.joinTime = joinTime; ++ this.leftTime = leftTime; ++ } ++ ++ /** ++ * Returns the bare JID of the Agent that was involved in the conversation. ++ * ++ * @return the bared JID of the Agent that was involved in the conversation. ++ */ ++ public String getAgentJID() { ++ return agentJID; ++ } ++ ++ /** ++ * Returns the Date when the Agent joined the conversation. ++ * ++ * @return the Date when the Agent joined the conversation. ++ */ ++ public Date getJoinTime() { ++ return joinTime; ++ } ++ ++ /** ++ * Returns the Date when the Agent left the conversation. ++ * ++ * @return the Date when the Agent left the conversation. ++ */ ++ public Date getLeftTime() { ++ return leftTime; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append(""); ++ ++ if (agentJID != null) { ++ buf.append("").append(agentJID).append(""); ++ } ++ if (joinTime != null) { ++ buf.append("").append(UTC_FORMAT.format(joinTime)).append(""); ++ } ++ if (leftTime != null) { ++ buf.append("").append(UTC_FORMAT.format(leftTime)).append(""); ++ } ++ buf.append(""); ++ ++ return buf.toString(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/TranscriptsProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/TranscriptsProvider.java +new file mode 100644 +index 0000000..cb8f429 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/TranscriptsProvider.java +@@ -0,0 +1,148 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.xmlpull.v1.XmlPullParser; ++import org.xmlpull.v1.XmlPullParserException; ++ ++import java.io.IOException; ++import java.text.ParseException; ++import java.text.SimpleDateFormat; ++import java.util.ArrayList; ++import java.util.Date; ++import java.util.List; ++import java.util.TimeZone; ++ ++/** ++ * An IQProvider for transcripts summaries. ++ * ++ * @author Gaston Dombiak ++ */ ++public class TranscriptsProvider implements IQProvider { ++ ++ private static final SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); ++ static { ++ UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+0")); ++ } ++ ++ public TranscriptsProvider() { ++ super(); ++ } ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ String userID = parser.getAttributeValue("", "userID"); ++ List summaries = new ArrayList(); ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("transcript")) { ++ summaries.add(parseSummary(parser)); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("transcripts")) { ++ done = true; ++ } ++ } ++ } ++ ++ return new Transcripts(userID, summaries); ++ } ++ ++ private Transcripts.TranscriptSummary parseSummary(XmlPullParser parser) throws IOException, ++ XmlPullParserException { ++ String sessionID = parser.getAttributeValue("", "sessionID"); ++ Date joinTime = null; ++ Date leftTime = null; ++ List agents = new ArrayList(); ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("joinTime")) { ++ try { ++ joinTime = UTC_FORMAT.parse(parser.nextText()); ++ } catch (ParseException e) {} ++ } ++ else if (parser.getName().equals("leftTime")) { ++ try { ++ leftTime = UTC_FORMAT.parse(parser.nextText()); ++ } catch (ParseException e) {} ++ } ++ else if (parser.getName().equals("agents")) { ++ agents = parseAgents(parser); ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("transcript")) { ++ done = true; ++ } ++ } ++ } ++ ++ return new Transcripts.TranscriptSummary(sessionID, joinTime, leftTime, agents); ++ } ++ ++ private List parseAgents(XmlPullParser parser) throws IOException, XmlPullParserException { ++ List agents = new ArrayList(); ++ String agentJID = null; ++ Date joinTime = null; ++ Date leftTime = null; ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if (eventType == XmlPullParser.START_TAG) { ++ if (parser.getName().equals("agentJID")) { ++ agentJID = parser.nextText(); ++ } ++ else if (parser.getName().equals("joinTime")) { ++ try { ++ joinTime = UTC_FORMAT.parse(parser.nextText()); ++ } catch (ParseException e) {} ++ } ++ else if (parser.getName().equals("leftTime")) { ++ try { ++ leftTime = UTC_FORMAT.parse(parser.nextText()); ++ } catch (ParseException e) {} ++ } ++ else if (parser.getName().equals("agent")) { ++ agentJID = null; ++ joinTime = null; ++ leftTime = null; ++ } ++ } ++ else if (eventType == XmlPullParser.END_TAG) { ++ if (parser.getName().equals("agents")) { ++ done = true; ++ } ++ else if (parser.getName().equals("agent")) { ++ agents.add(new Transcripts.AgentDetail(agentJID, joinTime, leftTime)); ++ } ++ } ++ } ++ return agents; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/UserID.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/UserID.java +new file mode 100644 +index 0000000..8bf4589 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/UserID.java +@@ -0,0 +1,77 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++public class UserID implements PacketExtension { ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "user"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; ++ ++ private String userID; ++ ++ public UserID(String userID) { ++ this.userID = userID; ++ } ++ ++ public String getUserID() { ++ return this.userID; ++ } ++ ++ public String getElementName() { ++ return ELEMENT_NAME; ++ } ++ ++ public String getNamespace() { ++ return NAMESPACE; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\" "); ++ buf.append("id=\"").append(this.getUserID()); ++ buf.append("\"/>"); ++ ++ return buf.toString(); ++ } ++ ++ public static class Provider implements PacketExtensionProvider { ++ ++ public PacketExtension parseExtension(XmlPullParser parser) throws Exception { ++ String userID = parser.getAttributeValue("", "id"); ++ ++ // Advance to end of extension. ++ parser.next(); ++ ++ return new UserID(userID); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/WorkgroupInformation.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/WorkgroupInformation.java +new file mode 100644 +index 0000000..b0ea447 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/WorkgroupInformation.java +@@ -0,0 +1,86 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.packet; ++ ++import org.jivesoftware.smack.packet.PacketExtension; ++import org.jivesoftware.smack.provider.PacketExtensionProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++/** ++ * A packet extension that contains information about the user and agent in a ++ * workgroup chat. The packet extension is attached to group chat invitations. ++ */ ++public class WorkgroupInformation implements PacketExtension { ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "workgroup"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jabber.org/protocol/workgroup"; ++ ++ private String workgroupJID; ++ ++ public WorkgroupInformation(String workgroupJID){ ++ this.workgroupJID = workgroupJID; ++ } ++ ++ public String getWorkgroupJID() { ++ return workgroupJID; ++ } ++ ++ public String getElementName() { ++ return ELEMENT_NAME; ++ } ++ ++ public String getNamespace() { ++ return NAMESPACE; ++ } ++ ++ public String toXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append('<').append(ELEMENT_NAME); ++ buf.append(" jid=\"").append(getWorkgroupJID()).append("\""); ++ buf.append(" xmlns=\"").append(NAMESPACE).append("\" />"); ++ ++ return buf.toString(); ++ } ++ ++ public static class Provider implements PacketExtensionProvider { ++ ++ /** ++ * PacketExtensionProvider implementation ++ */ ++ public PacketExtension parseExtension (XmlPullParser parser) ++ throws Exception { ++ String workgroupJID = parser.getAttributeValue("", "jid"); ++ ++ // since this is a start and end tag, and we arrive on the start, this should guarantee ++ // we leave on the end ++ parser.next(); ++ ++ return new WorkgroupInformation(workgroupJID); ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/ChatSetting.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/ChatSetting.java +new file mode 100644 +index 0000000..921134a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/ChatSetting.java +@@ -0,0 +1,56 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.settings; ++ ++public class ChatSetting { ++ private String key; ++ private String value; ++ private int type; ++ ++ public ChatSetting(String key, String value, int type){ ++ setKey(key); ++ setValue(value); ++ setType(type); ++ } ++ ++ public String getKey() { ++ return key; ++ } ++ ++ public void setKey(String key) { ++ this.key = key; ++ } ++ ++ public String getValue() { ++ return value; ++ } ++ ++ public void setValue(String value) { ++ this.value = value; ++ } ++ ++ public int getType() { ++ return type; ++ } ++ ++ public void setType(int type) { ++ this.type = type; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/ChatSettings.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/ChatSettings.java +new file mode 100644 +index 0000000..60e4521 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/ChatSettings.java +@@ -0,0 +1,179 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.settings; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.Iterator; ++import java.util.List; ++ ++public class ChatSettings extends IQ { ++ ++ /** ++ * Defined as image type. ++ */ ++ public static final int IMAGE_SETTINGS = 0; ++ ++ /** ++ * Defined as Text settings type. ++ */ ++ public static final int TEXT_SETTINGS = 1; ++ ++ /** ++ * Defined as Bot settings type. ++ */ ++ public static final int BOT_SETTINGS = 2; ++ ++ private List settings; ++ private String key; ++ private int type = -1; ++ ++ public ChatSettings() { ++ settings = new ArrayList(); ++ } ++ ++ public ChatSettings(String key) { ++ setKey(key); ++ } ++ ++ public void setKey(String key) { ++ this.key = key; ++ } ++ ++ public void setType(int type) { ++ this.type = type; ++ } ++ ++ public void addSetting(ChatSetting setting) { ++ settings.add(setting); ++ } ++ ++ public Collection getSettings() { ++ return settings; ++ } ++ ++ public ChatSetting getChatSetting(String key) { ++ Collection col = getSettings(); ++ if (col != null) { ++ Iterator iter = col.iterator(); ++ while (iter.hasNext()) { ++ ChatSetting chatSetting = (ChatSetting)iter.next(); ++ if (chatSetting.getKey().equals(key)) { ++ return chatSetting; ++ } ++ } ++ } ++ return null; ++ } ++ ++ public ChatSetting getFirstEntry() { ++ if (settings.size() > 0) { ++ return (ChatSetting)settings.get(0); ++ } ++ return null; ++ } ++ ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "chat-settings"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns="); ++ buf.append('"'); ++ buf.append(NAMESPACE); ++ buf.append('"'); ++ if (key != null) { ++ buf.append(" key=\"" + key + "\""); ++ } ++ ++ if (type != -1) { ++ buf.append(" type=\"" + type + "\""); ++ } ++ ++ buf.append("> "); ++ return buf.toString(); ++ } ++ ++ /** ++ * Packet extension provider for AgentStatusRequest packets. ++ */ ++ public static class InternalProvider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ if (parser.getEventType() != XmlPullParser.START_TAG) { ++ throw new IllegalStateException("Parser not in proper position, or bad XML."); ++ } ++ ++ ChatSettings chatSettings = new ChatSettings(); ++ ++ boolean done = false; ++ while (!done) { ++ int eventType = parser.next(); ++ if ((eventType == XmlPullParser.START_TAG) && ("chat-setting".equals(parser.getName()))) { ++ chatSettings.addSetting(parseChatSetting(parser)); ++ ++ } ++ else if (eventType == XmlPullParser.END_TAG && ELEMENT_NAME.equals(parser.getName())) { ++ done = true; ++ } ++ } ++ return chatSettings; ++ } ++ ++ private ChatSetting parseChatSetting(XmlPullParser parser) throws Exception { ++ ++ boolean done = false; ++ String key = null; ++ String value = null; ++ int type = 0; ++ ++ while (!done) { ++ int eventType = parser.next(); ++ if ((eventType == XmlPullParser.START_TAG) && ("key".equals(parser.getName()))) { ++ key = parser.nextText(); ++ } ++ else if ((eventType == XmlPullParser.START_TAG) && ("value".equals(parser.getName()))) { ++ value = parser.nextText(); ++ } ++ else if ((eventType == XmlPullParser.START_TAG) && ("type".equals(parser.getName()))) { ++ type = Integer.parseInt(parser.nextText()); ++ } ++ else if (eventType == XmlPullParser.END_TAG && "chat-setting".equals(parser.getName())) { ++ done = true; ++ } ++ } ++ return new ChatSetting(key, value, type); ++ } ++ } ++} ++ +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/GenericSettings.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/GenericSettings.java +new file mode 100644 +index 0000000..dcbd32b +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/GenericSettings.java +@@ -0,0 +1,114 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.settings; ++ ++import org.jivesoftware.smackx.workgroup.util.ModelUtil; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++import java.util.HashMap; ++import java.util.Map; ++ ++public class GenericSettings extends IQ { ++ ++ private Map map = new HashMap(); ++ ++ private String query; ++ ++ public String getQuery() { ++ return query; ++ } ++ ++ public void setQuery(String query) { ++ this.query = query; ++ } ++ ++ public Map getMap() { ++ return map; ++ } ++ ++ public void setMap(Map map) { ++ this.map = map; ++ } ++ ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "generic-metadata"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns="); ++ buf.append('"'); ++ buf.append(NAMESPACE); ++ buf.append('"'); ++ buf.append(">"); ++ if (ModelUtil.hasLength(getQuery())) { ++ buf.append("" + getQuery() + ""); ++ } ++ buf.append(" "); ++ return buf.toString(); ++ } ++ ++ ++ /** ++ * Packet extension provider for SoundSetting Packets. ++ */ ++ public static class InternalProvider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ if (parser.getEventType() != XmlPullParser.START_TAG) { ++ throw new IllegalStateException("Parser not in proper position, or bad XML."); ++ } ++ ++ GenericSettings setting = new GenericSettings(); ++ ++ boolean done = false; ++ ++ ++ while (!done) { ++ int eventType = parser.next(); ++ if ((eventType == XmlPullParser.START_TAG) && ("entry".equals(parser.getName()))) { ++ eventType = parser.next(); ++ String name = parser.nextText(); ++ eventType = parser.next(); ++ String value = parser.nextText(); ++ setting.getMap().put(name, value); ++ } ++ else if (eventType == XmlPullParser.END_TAG && ELEMENT_NAME.equals(parser.getName())) { ++ done = true; ++ } ++ } ++ ++ return setting; ++ } ++ } ++ ++ ++} ++ +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/OfflineSettings.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/OfflineSettings.java +new file mode 100644 +index 0000000..15136fd +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/OfflineSettings.java +@@ -0,0 +1,155 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.settings; ++ ++import org.jivesoftware.smackx.workgroup.util.ModelUtil; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++public class OfflineSettings extends IQ { ++ private String redirectURL; ++ ++ private String offlineText; ++ private String emailAddress; ++ private String subject; ++ ++ public String getRedirectURL() { ++ if (!ModelUtil.hasLength(redirectURL)) { ++ return ""; ++ } ++ return redirectURL; ++ } ++ ++ public void setRedirectURL(String redirectURL) { ++ this.redirectURL = redirectURL; ++ } ++ ++ public String getOfflineText() { ++ if (!ModelUtil.hasLength(offlineText)) { ++ return ""; ++ } ++ return offlineText; ++ } ++ ++ public void setOfflineText(String offlineText) { ++ this.offlineText = offlineText; ++ } ++ ++ public String getEmailAddress() { ++ if (!ModelUtil.hasLength(emailAddress)) { ++ return ""; ++ } ++ return emailAddress; ++ } ++ ++ public void setEmailAddress(String emailAddress) { ++ this.emailAddress = emailAddress; ++ } ++ ++ public String getSubject() { ++ if (!ModelUtil.hasLength(subject)) { ++ return ""; ++ } ++ return subject; ++ } ++ ++ public void setSubject(String subject) { ++ this.subject = subject; ++ } ++ ++ public boolean redirects() { ++ return (ModelUtil.hasLength(getRedirectURL())); ++ } ++ ++ public boolean isConfigured(){ ++ return ModelUtil.hasLength(getEmailAddress()) && ++ ModelUtil.hasLength(getSubject()) && ++ ModelUtil.hasLength(getOfflineText()); ++ } ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "offline-settings"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns="); ++ buf.append('"'); ++ buf.append(NAMESPACE); ++ buf.append('"'); ++ buf.append("> "); ++ return buf.toString(); ++ } ++ ++ ++ /** ++ * Packet extension provider for AgentStatusRequest packets. ++ */ ++ public static class InternalProvider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ if (parser.getEventType() != XmlPullParser.START_TAG) { ++ throw new IllegalStateException("Parser not in proper position, or bad XML."); ++ } ++ ++ OfflineSettings offlineSettings = new OfflineSettings(); ++ ++ boolean done = false; ++ String redirectPage = null; ++ String subject = null; ++ String offlineText = null; ++ String emailAddress = null; ++ ++ while (!done) { ++ int eventType = parser.next(); ++ if ((eventType == XmlPullParser.START_TAG) && ("redirectPage".equals(parser.getName()))) { ++ redirectPage = parser.nextText(); ++ } ++ else if ((eventType == XmlPullParser.START_TAG) && ("subject".equals(parser.getName()))) { ++ subject = parser.nextText(); ++ } ++ else if ((eventType == XmlPullParser.START_TAG) && ("offlineText".equals(parser.getName()))) { ++ offlineText = parser.nextText(); ++ } ++ else if ((eventType == XmlPullParser.START_TAG) && ("emailAddress".equals(parser.getName()))) { ++ emailAddress = parser.nextText(); ++ } ++ else if (eventType == XmlPullParser.END_TAG && "offline-settings".equals(parser.getName())) { ++ done = true; ++ } ++ } ++ ++ offlineSettings.setEmailAddress(emailAddress); ++ offlineSettings.setRedirectURL(redirectPage); ++ offlineSettings.setSubject(subject); ++ offlineSettings.setOfflineText(offlineText); ++ return offlineSettings; ++ } ++ } ++} ++ +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/SearchSettings.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/SearchSettings.java +new file mode 100644 +index 0000000..98d59fc +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/SearchSettings.java +@@ -0,0 +1,112 @@ ++/** ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.settings; ++ ++import org.jivesoftware.smackx.workgroup.util.ModelUtil; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++public class SearchSettings extends IQ { ++ private String forumsLocation; ++ private String kbLocation; ++ ++ public boolean isSearchEnabled() { ++ return ModelUtil.hasLength(getForumsLocation()) && ModelUtil.hasLength(getKbLocation()); ++ } ++ ++ public String getForumsLocation() { ++ return forumsLocation; ++ } ++ ++ public void setForumsLocation(String forumsLocation) { ++ this.forumsLocation = forumsLocation; ++ } ++ ++ public String getKbLocation() { ++ return kbLocation; ++ } ++ ++ public void setKbLocation(String kbLocation) { ++ this.kbLocation = kbLocation; ++ } ++ ++ public boolean hasKB(){ ++ return ModelUtil.hasLength(getKbLocation()); ++ } ++ ++ public boolean hasForums(){ ++ return ModelUtil.hasLength(getForumsLocation()); ++ } ++ ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "search-settings"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns="); ++ buf.append('"'); ++ buf.append(NAMESPACE); ++ buf.append('"'); ++ buf.append("> "); ++ return buf.toString(); ++ } ++ ++ ++ /** ++ * Packet extension provider for AgentStatusRequest packets. ++ */ ++ public static class InternalProvider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ if (parser.getEventType() != XmlPullParser.START_TAG) { ++ throw new IllegalStateException("Parser not in proper position, or bad XML."); ++ } ++ ++ SearchSettings settings = new SearchSettings(); ++ ++ boolean done = false; ++ String kb = null; ++ String forums = null; ++ ++ while (!done) { ++ int eventType = parser.next(); ++ if ((eventType == XmlPullParser.START_TAG) && ("forums".equals(parser.getName()))) { ++ forums = parser.nextText(); ++ } ++ else if ((eventType == XmlPullParser.START_TAG) && ("kb".equals(parser.getName()))) { ++ kb = parser.nextText(); ++ } ++ else if (eventType == XmlPullParser.END_TAG && "search-settings".equals(parser.getName())) { ++ done = true; ++ } ++ } ++ ++ settings.setForumsLocation(forums); ++ settings.setKbLocation(kb); ++ return settings; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/SoundSettings.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/SoundSettings.java +new file mode 100644 +index 0000000..66bec35 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/SoundSettings.java +@@ -0,0 +1,103 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.settings; ++ ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.jivesoftware.smack.util.StringUtils; ++import org.xmlpull.v1.XmlPullParser; ++ ++public class SoundSettings extends IQ { ++ private String outgoingSound; ++ private String incomingSound; ++ ++ ++ public void setOutgoingSound(String outgoingSound) { ++ this.outgoingSound = outgoingSound; ++ } ++ ++ public void setIncomingSound(String incomingSound) { ++ this.incomingSound = incomingSound; ++ } ++ ++ public byte[] getIncomingSoundBytes() { ++ return StringUtils.decodeBase64(incomingSound); ++ } ++ ++ public byte[] getOutgoingSoundBytes() { ++ return StringUtils.decodeBase64(outgoingSound); ++ } ++ ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "sound-settings"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns="); ++ buf.append('"'); ++ buf.append(NAMESPACE); ++ buf.append('"'); ++ buf.append("> "); ++ return buf.toString(); ++ } ++ ++ ++ /** ++ * Packet extension provider for SoundSetting Packets. ++ */ ++ public static class InternalProvider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ if (parser.getEventType() != XmlPullParser.START_TAG) { ++ throw new IllegalStateException("Parser not in proper position, or bad XML."); ++ } ++ ++ SoundSettings soundSettings = new SoundSettings(); ++ ++ boolean done = false; ++ ++ ++ while (!done) { ++ int eventType = parser.next(); ++ if ((eventType == XmlPullParser.START_TAG) && ("outgoingSound".equals(parser.getName()))) { ++ soundSettings.setOutgoingSound(parser.nextText()); ++ } ++ else if ((eventType == XmlPullParser.START_TAG) && ("incomingSound".equals(parser.getName()))) { ++ soundSettings.setIncomingSound(parser.nextText()); ++ } ++ else if (eventType == XmlPullParser.END_TAG && "sound-settings".equals(parser.getName())) { ++ done = true; ++ } ++ } ++ ++ return soundSettings; ++ } ++ } ++} ++ +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/WorkgroupProperties.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/WorkgroupProperties.java +new file mode 100644 +index 0000000..8e405bb +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/WorkgroupProperties.java +@@ -0,0 +1,125 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.settings; ++ ++import org.jivesoftware.smackx.workgroup.util.ModelUtil; ++import org.jivesoftware.smack.packet.IQ; ++import org.jivesoftware.smack.provider.IQProvider; ++import org.xmlpull.v1.XmlPullParser; ++ ++public class WorkgroupProperties extends IQ { ++ ++ private boolean authRequired; ++ private String email; ++ private String fullName; ++ private String jid; ++ ++ public boolean isAuthRequired() { ++ return authRequired; ++ } ++ ++ public void setAuthRequired(boolean authRequired) { ++ this.authRequired = authRequired; ++ } ++ ++ public String getEmail() { ++ return email; ++ } ++ ++ public void setEmail(String email) { ++ this.email = email; ++ } ++ ++ public String getFullName() { ++ return fullName; ++ } ++ ++ public void setFullName(String fullName) { ++ this.fullName = fullName; ++ } ++ ++ public String getJid() { ++ return jid; ++ } ++ ++ public void setJid(String jid) { ++ this.jid = jid; ++ } ++ ++ ++ /** ++ * Element name of the packet extension. ++ */ ++ public static final String ELEMENT_NAME = "workgroup-properties"; ++ ++ /** ++ * Namespace of the packet extension. ++ */ ++ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append("<").append(ELEMENT_NAME).append(" xmlns="); ++ buf.append('"'); ++ buf.append(NAMESPACE); ++ buf.append('"'); ++ if (ModelUtil.hasLength(getJid())) { ++ buf.append("jid=\"" + getJid() + "\" "); ++ } ++ buf.append("> "); ++ return buf.toString(); ++ } ++ ++ /** ++ * Packet extension provider for SoundSetting Packets. ++ */ ++ public static class InternalProvider implements IQProvider { ++ ++ public IQ parseIQ(XmlPullParser parser) throws Exception { ++ if (parser.getEventType() != XmlPullParser.START_TAG) { ++ throw new IllegalStateException("Parser not in proper position, or bad XML."); ++ } ++ ++ WorkgroupProperties props = new WorkgroupProperties(); ++ ++ boolean done = false; ++ ++ ++ while (!done) { ++ int eventType = parser.next(); ++ if ((eventType == XmlPullParser.START_TAG) && ("authRequired".equals(parser.getName()))) { ++ props.setAuthRequired(new Boolean(parser.nextText()).booleanValue()); ++ } ++ else if ((eventType == XmlPullParser.START_TAG) && ("email".equals(parser.getName()))) { ++ props.setEmail(parser.nextText()); ++ } ++ else if ((eventType == XmlPullParser.START_TAG) && ("name".equals(parser.getName()))) { ++ props.setFullName(parser.nextText()); ++ } ++ else if (eventType == XmlPullParser.END_TAG && "workgroup-properties".equals(parser.getName())) { ++ done = true; ++ } ++ } ++ ++ return props; ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/user/QueueListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/user/QueueListener.java +new file mode 100644 +index 0000000..fa3e6a6 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/user/QueueListener.java +@@ -0,0 +1,55 @@ ++/** ++ * $Revision$ ++ * $Date$ ++ * ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.user; ++ ++/** ++ * Listener interface for those that wish to be notified of workgroup queue events. ++ * ++ * @see Workgroup#addQueueListener(QueueListener) ++ * @author loki der quaeler ++ */ ++public interface QueueListener { ++ ++ /** ++ * The user joined the workgroup queue. ++ */ ++ public void joinedQueue(); ++ ++ /** ++ * The user departed the workgroup queue. ++ */ ++ public void departedQueue(); ++ ++ /** ++ * The user's queue position has been updated to a new value. ++ * ++ * @param currentPosition the user's current position in the queue. ++ */ ++ public void queuePositionUpdated(int currentPosition); ++ ++ /** ++ * The user's estimated remaining wait time in the queue has been updated. ++ * ++ * @param secondsRemaining the estimated number of seconds remaining until the ++ * the user is routed to the agent. ++ */ ++ public void queueWaitTimeUpdated(int secondsRemaining); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/user/Workgroup.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/user/Workgroup.java +new file mode 100644 +index 0000000..a21b996 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/user/Workgroup.java +@@ -0,0 +1,870 @@ ++/** ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.user; ++ ++import org.jivesoftware.smackx.workgroup.MetaData; ++import org.jivesoftware.smackx.workgroup.WorkgroupInvitation; ++import org.jivesoftware.smackx.workgroup.WorkgroupInvitationListener; ++import org.jivesoftware.smackx.workgroup.ext.forms.WorkgroupForm; ++import org.jivesoftware.smackx.workgroup.packet.DepartQueuePacket; ++import org.jivesoftware.smackx.workgroup.packet.QueueUpdate; ++import org.jivesoftware.smackx.workgroup.packet.SessionID; ++import org.jivesoftware.smackx.workgroup.packet.UserID; ++import org.jivesoftware.smackx.workgroup.settings.*; ++import org.jivesoftware.smack.*; ++import org.jivesoftware.smack.filter.*; ++import org.jivesoftware.smack.packet.*; ++import org.jivesoftware.smack.util.StringUtils; ++import org.jivesoftware.smackx.Form; ++import org.jivesoftware.smackx.FormField; ++import org.jivesoftware.smackx.ServiceDiscoveryManager; ++import org.jivesoftware.smackx.muc.MultiUserChat; ++import org.jivesoftware.smackx.packet.DataForm; ++import org.jivesoftware.smackx.packet.DiscoverInfo; ++import org.jivesoftware.smackx.packet.MUCUser; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++ ++/** ++ * Provides workgroup services for users. Users can join the workgroup queue, depart the ++ * queue, find status information about their placement in the queue, and register to ++ * be notified when they are routed to an agent.

      ++ *

      ++ * This class only provides a users perspective into a workgroup and is not intended ++ * for use by agents. ++ * ++ * @author Matt Tucker ++ * @author Derek DeMoro ++ */ ++public class Workgroup { ++ ++ private String workgroupJID; ++ private Connection connection; ++ private boolean inQueue; ++ private List invitationListeners; ++ private List queueListeners; ++ private List siteInviteListeners; ++ ++ private int queuePosition = -1; ++ private int queueRemainingTime = -1; ++ ++ /** ++ * Creates a new workgroup instance using the specified workgroup JID ++ * (eg support@workgroup.example.com) and XMPP connection. The connection must have ++ * undergone a successful login before being used to construct an instance of ++ * this class. ++ * ++ * @param workgroupJID the JID of the workgroup. ++ * @param connection an XMPP connection which must have already undergone a ++ * successful login. ++ */ ++ public Workgroup(String workgroupJID, Connection connection) { ++ // Login must have been done before passing in connection. ++ if (!connection.isAuthenticated()) { ++ throw new IllegalStateException("Must login to server before creating workgroup."); ++ } ++ ++ this.workgroupJID = workgroupJID; ++ this.connection = connection; ++ inQueue = false; ++ invitationListeners = new ArrayList(); ++ queueListeners = new ArrayList(); ++ siteInviteListeners = new ArrayList(); ++ ++ // Register as a queue listener for internal usage by this instance. ++ addQueueListener(new QueueListener() { ++ public void joinedQueue() { ++ inQueue = true; ++ } ++ ++ public void departedQueue() { ++ inQueue = false; ++ queuePosition = -1; ++ queueRemainingTime = -1; ++ } ++ ++ public void queuePositionUpdated(int currentPosition) { ++ queuePosition = currentPosition; ++ } ++ ++ public void queueWaitTimeUpdated(int secondsRemaining) { ++ queueRemainingTime = secondsRemaining; ++ } ++ }); ++ ++ /** ++ * Internal handling of an invitation.Recieving an invitation removes the user from the queue. ++ */ ++ MultiUserChat.addInvitationListener(connection, ++ new org.jivesoftware.smackx.muc.InvitationListener() { ++ public void invitationReceived(Connection conn, String room, String inviter, ++ String reason, String password, Message message) { ++ inQueue = false; ++ queuePosition = -1; ++ queueRemainingTime = -1; ++ } ++ }); ++ ++ // Register a packet listener for all the messages sent to this client. ++ PacketFilter typeFilter = new PacketTypeFilter(Message.class); ++ ++ connection.addPacketListener(new PacketListener() { ++ public void processPacket(Packet packet) { ++ handlePacket(packet); ++ } ++ }, typeFilter); ++ } ++ ++ /** ++ * Returns the name of this workgroup (eg support@example.com). ++ * ++ * @return the name of the workgroup. ++ */ ++ public String getWorkgroupJID() { ++ return workgroupJID; ++ } ++ ++ /** ++ * Returns true if the user is currently waiting in the workgroup queue. ++ * ++ * @return true if currently waiting in the queue. ++ */ ++ public boolean isInQueue() { ++ return inQueue; ++ } ++ ++ /** ++ * Returns true if the workgroup is available for receiving new requests. The workgroup will be ++ * available only when agents are available for this workgroup. ++ * ++ * @return true if the workgroup is available for receiving new requests. ++ */ ++ public boolean isAvailable() { ++ Presence directedPresence = new Presence(Presence.Type.available); ++ directedPresence.setTo(workgroupJID); ++ PacketFilter typeFilter = new PacketTypeFilter(Presence.class); ++ PacketFilter fromFilter = new FromContainsFilter(workgroupJID); ++ PacketCollector collector = connection.createPacketCollector(new AndFilter(fromFilter, ++ typeFilter)); ++ ++ connection.sendPacket(directedPresence); ++ ++ Presence response = (Presence)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ return false; ++ } ++ else if (response.getError() != null) { ++ return false; ++ } ++ else { ++ return Presence.Type.available == response.getType(); ++ } ++ } ++ ++ /** ++ * Returns the users current position in the workgroup queue. A value of 0 means ++ * the user is next in line to be routed; therefore, if the queue position ++ * is being displayed to the end user it is usually a good idea to add 1 to ++ * the value this method returns before display. If the user is not currently ++ * waiting in the workgroup, or no queue position information is available, -1 ++ * will be returned. ++ * ++ * @return the user's current position in the workgroup queue, or -1 if the ++ * position isn't available or if the user isn't in the queue. ++ */ ++ public int getQueuePosition() { ++ return queuePosition; ++ } ++ ++ /** ++ * Returns the estimated time (in seconds) that the user has to left wait in ++ * the workgroup queue before being routed. If the user is not currently waiting ++ * int he workgroup, or no queue time information is available, -1 will be ++ * returned. ++ * ++ * @return the estimated time remaining (in seconds) that the user has to ++ * wait inthe workgroupu queue, or -1 if time information isn't available ++ * or if the user isn't int the queue. ++ */ ++ public int getQueueRemainingTime() { ++ return queueRemainingTime; ++ } ++ ++ /** ++ * Joins the workgroup queue to wait to be routed to an agent. After joining ++ * the queue, queue status events will be sent to indicate the user's position and ++ * estimated time left in the queue. Once joining the queue, there are three ways ++ * the user can leave the queue:

        ++ *

        ++ *

      • The user is routed to an agent, which triggers a GroupChat invitation. ++ *
      • The user asks to leave the queue by calling the {@link #departQueue} method. ++ *
      • A server error occurs, or an administrator explicitly removes the user ++ * from the queue. ++ *
      ++ *

      ++ * A user cannot request to join the queue again if already in the queue. Therefore, ++ * this method will throw an IllegalStateException if the user is already in the queue.

      ++ *

      ++ * Some servers may be configured to require certain meta-data in order to ++ * join the queue. In that case, the {@link #joinQueue(Form)} method should be ++ * used instead of this method so that meta-data may be passed in.

      ++ *

      ++ * The server tracks the conversations that a user has with agents over time. By ++ * default, that tracking is done using the user's JID. However, this is not always ++ * possible. For example, when the user is logged in anonymously using a web client. ++ * In that case the user ID might be a randomly generated value put into a persistent ++ * cookie or a username obtained via the session. A userID can be explicitly ++ * passed in by using the {@link #joinQueue(Form, String)} method. When specified, ++ * that userID will be used instead of the user's JID to track conversations. The ++ * server will ignore a manually specified userID if the user's connection to the server ++ * is not anonymous. ++ * ++ * @throws XMPPException if an error occured joining the queue. An error may indicate ++ * that a connection failure occured or that the server explicitly rejected the ++ * request to join the queue. ++ */ ++ public void joinQueue() throws XMPPException { ++ joinQueue(null); ++ } ++ ++ /** ++ * Joins the workgroup queue to wait to be routed to an agent. After joining ++ * the queue, queue status events will be sent to indicate the user's position and ++ * estimated time left in the queue. Once joining the queue, there are three ways ++ * the user can leave the queue:

        ++ *

        ++ *

      • The user is routed to an agent, which triggers a GroupChat invitation. ++ *
      • The user asks to leave the queue by calling the {@link #departQueue} method. ++ *
      • A server error occurs, or an administrator explicitly removes the user ++ * from the queue. ++ *
      ++ *

      ++ * A user cannot request to join the queue again if already in the queue. Therefore, ++ * this method will throw an IllegalStateException if the user is already in the queue.

      ++ *

      ++ * Some servers may be configured to require certain meta-data in order to ++ * join the queue.

      ++ *

      ++ * The server tracks the conversations that a user has with agents over time. By ++ * default, that tracking is done using the user's JID. However, this is not always ++ * possible. For example, when the user is logged in anonymously using a web client. ++ * In that case the user ID might be a randomly generated value put into a persistent ++ * cookie or a username obtained via the session. A userID can be explicitly ++ * passed in by using the {@link #joinQueue(Form, String)} method. When specified, ++ * that userID will be used instead of the user's JID to track conversations. The ++ * server will ignore a manually specified userID if the user's connection to the server ++ * is not anonymous. ++ * ++ * @param answerForm the completed form the send for the join request. ++ * @throws XMPPException if an error occured joining the queue. An error may indicate ++ * that a connection failure occured or that the server explicitly rejected the ++ * request to join the queue. ++ */ ++ public void joinQueue(Form answerForm) throws XMPPException { ++ joinQueue(answerForm, null); ++ } ++ ++ /** ++ *

      Joins the workgroup queue to wait to be routed to an agent. After joining ++ * the queue, queue status events will be sent to indicate the user's position and ++ * estimated time left in the queue. Once joining the queue, there are three ways ++ * the user can leave the queue:

        ++ *

        ++ *

      • The user is routed to an agent, which triggers a GroupChat invitation. ++ *
      • The user asks to leave the queue by calling the {@link #departQueue} method. ++ *
      • A server error occurs, or an administrator explicitly removes the user ++ * from the queue. ++ *
      ++ *

      ++ * A user cannot request to join the queue again if already in the queue. Therefore, ++ * this method will throw an IllegalStateException if the user is already in the queue.

      ++ *

      ++ * Some servers may be configured to require certain meta-data in order to ++ * join the queue.

      ++ *

      ++ * The server tracks the conversations that a user has with agents over time. By ++ * default, that tracking is done using the user's JID. However, this is not always ++ * possible. For example, when the user is logged in anonymously using a web client. ++ * In that case the user ID might be a randomly generated value put into a persistent ++ * cookie or a username obtained via the session. When specified, that userID will ++ * be used instead of the user's JID to track conversations. The server will ignore a ++ * manually specified userID if the user's connection to the server is not anonymous. ++ * ++ * @param answerForm the completed form associated with the join reqest. ++ * @param userID String that represents the ID of the user when using anonymous sessions ++ * or null if a userID should not be used. ++ * @throws XMPPException if an error occured joining the queue. An error may indicate ++ * that a connection failure occured or that the server explicitly rejected the ++ * request to join the queue. ++ */ ++ public void joinQueue(Form answerForm, String userID) throws XMPPException { ++ // If already in the queue ignore the join request. ++ if (inQueue) { ++ throw new IllegalStateException("Already in queue " + workgroupJID); ++ } ++ ++ JoinQueuePacket joinPacket = new JoinQueuePacket(workgroupJID, answerForm, userID); ++ ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(joinPacket.getPacketID())); ++ ++ this.connection.sendPacket(joinPacket); ++ ++ IQ response = (IQ)collector.nextResult(10000); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ ++ // Notify listeners that we've joined the queue. ++ fireQueueJoinedEvent(); ++ } ++ ++ /** ++ *

      Joins the workgroup queue to wait to be routed to an agent. After joining ++ * the queue, queue status events will be sent to indicate the user's position and ++ * estimated time left in the queue. Once joining the queue, there are three ways ++ * the user can leave the queue:

        ++ *

        ++ *

      • The user is routed to an agent, which triggers a GroupChat invitation. ++ *
      • The user asks to leave the queue by calling the {@link #departQueue} method. ++ *
      • A server error occurs, or an administrator explicitly removes the user ++ * from the queue. ++ *
      ++ *

      ++ * A user cannot request to join the queue again if already in the queue. Therefore, ++ * this method will throw an IllegalStateException if the user is already in the queue.

      ++ *

      ++ * Some servers may be configured to require certain meta-data in order to ++ * join the queue.

      ++ *

      ++ * The server tracks the conversations that a user has with agents over time. By ++ * default, that tracking is done using the user's JID. However, this is not always ++ * possible. For example, when the user is logged in anonymously using a web client. ++ * In that case the user ID might be a randomly generated value put into a persistent ++ * cookie or a username obtained via the session. When specified, that userID will ++ * be used instead of the user's JID to track conversations. The server will ignore a ++ * manually specified userID if the user's connection to the server is not anonymous. ++ * ++ * @param metadata metadata to create a dataform from. ++ * @param userID String that represents the ID of the user when using anonymous sessions ++ * or null if a userID should not be used. ++ * @throws XMPPException if an error occured joining the queue. An error may indicate ++ * that a connection failure occured or that the server explicitly rejected the ++ * request to join the queue. ++ */ ++ public void joinQueue(Map metadata, String userID) throws XMPPException { ++ // If already in the queue ignore the join request. ++ if (inQueue) { ++ throw new IllegalStateException("Already in queue " + workgroupJID); ++ } ++ ++ // Build dataform from metadata ++ Form form = new Form(Form.TYPE_SUBMIT); ++ Iterator iter = metadata.keySet().iterator(); ++ while (iter.hasNext()) { ++ String name = (String)iter.next(); ++ String value = (String)metadata.get(name).toString(); ++ ++ String escapedName = StringUtils.escapeForXML(name); ++ String escapedValue = StringUtils.escapeForXML(value); ++ ++ FormField field = new FormField(escapedName); ++ field.setType(FormField.TYPE_TEXT_SINGLE); ++ form.addField(field); ++ form.setAnswer(escapedName, escapedValue); ++ } ++ joinQueue(form, userID); ++ } ++ ++ /** ++ * Departs the workgroup queue. If the user is not currently in the queue, this ++ * method will do nothing.

      ++ *

      ++ * Normally, the user would not manually leave the queue. However, they may wish to ++ * under certain circumstances -- for example, if they no longer wish to be routed ++ * to an agent because they've been waiting too long. ++ * ++ * @throws XMPPException if an error occured trying to send the depart queue ++ * request to the server. ++ */ ++ public void departQueue() throws XMPPException { ++ // If not in the queue ignore the depart request. ++ if (!inQueue) { ++ return; ++ } ++ ++ DepartQueuePacket departPacket = new DepartQueuePacket(this.workgroupJID); ++ PacketCollector collector = this.connection.createPacketCollector(new PacketIDFilter(departPacket.getPacketID())); ++ ++ connection.sendPacket(departPacket); ++ ++ IQ response = (IQ)collector.nextResult(5000); ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from the server."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ ++ // Notify listeners that we're no longer in the queue. ++ fireQueueDepartedEvent(); ++ } ++ ++ /** ++ * Adds a queue listener that will be notified of queue events for the user ++ * that created this Workgroup instance. ++ * ++ * @param queueListener the queue listener. ++ */ ++ public void addQueueListener(QueueListener queueListener) { ++ synchronized (queueListeners) { ++ if (!queueListeners.contains(queueListener)) { ++ queueListeners.add(queueListener); ++ } ++ } ++ } ++ ++ /** ++ * Removes a queue listener. ++ * ++ * @param queueListener the queue listener. ++ */ ++ public void removeQueueListener(QueueListener queueListener) { ++ synchronized (queueListeners) { ++ queueListeners.remove(queueListener); ++ } ++ } ++ ++ /** ++ * Adds an invitation listener that will be notified of groupchat invitations ++ * from the workgroup for the the user that created this Workgroup instance. ++ * ++ * @param invitationListener the invitation listener. ++ */ ++ public void addInvitationListener(WorkgroupInvitationListener invitationListener) { ++ synchronized (invitationListeners) { ++ if (!invitationListeners.contains(invitationListener)) { ++ invitationListeners.add(invitationListener); ++ } ++ } ++ } ++ ++ /** ++ * Removes an invitation listener. ++ * ++ * @param invitationListener the invitation listener. ++ */ ++ public void removeQueueListener(WorkgroupInvitationListener invitationListener) { ++ synchronized (invitationListeners) { ++ invitationListeners.remove(invitationListener); ++ } ++ } ++ ++ private void fireInvitationEvent(WorkgroupInvitation invitation) { ++ synchronized (invitationListeners) { ++ for (Iterator i = invitationListeners.iterator(); i.hasNext();) { ++ WorkgroupInvitationListener listener = (WorkgroupInvitationListener)i.next(); ++ listener.invitationReceived(invitation); ++ } ++ } ++ } ++ ++ private void fireQueueJoinedEvent() { ++ synchronized (queueListeners) { ++ for (Iterator i = queueListeners.iterator(); i.hasNext();) { ++ QueueListener listener = (QueueListener)i.next(); ++ listener.joinedQueue(); ++ } ++ } ++ } ++ ++ private void fireQueueDepartedEvent() { ++ synchronized (queueListeners) { ++ for (Iterator i = queueListeners.iterator(); i.hasNext();) { ++ QueueListener listener = (QueueListener)i.next(); ++ listener.departedQueue(); ++ } ++ } ++ } ++ ++ private void fireQueuePositionEvent(int currentPosition) { ++ synchronized (queueListeners) { ++ for (Iterator i = queueListeners.iterator(); i.hasNext();) { ++ QueueListener listener = (QueueListener)i.next(); ++ listener.queuePositionUpdated(currentPosition); ++ } ++ } ++ } ++ ++ private void fireQueueTimeEvent(int secondsRemaining) { ++ synchronized (queueListeners) { ++ for (Iterator i = queueListeners.iterator(); i.hasNext();) { ++ QueueListener listener = (QueueListener)i.next(); ++ listener.queueWaitTimeUpdated(secondsRemaining); ++ } ++ } ++ } ++ ++ // PacketListener Implementation. ++ ++ private void handlePacket(Packet packet) { ++ if (packet instanceof Message) { ++ Message msg = (Message)packet; ++ // Check to see if the user left the queue. ++ PacketExtension pe = msg.getExtension("depart-queue", "http://jabber.org/protocol/workgroup"); ++ PacketExtension queueStatus = msg.getExtension("queue-status", "http://jabber.org/protocol/workgroup"); ++ ++ if (pe != null) { ++ fireQueueDepartedEvent(); ++ } ++ else if (queueStatus != null) { ++ QueueUpdate queueUpdate = (QueueUpdate)queueStatus; ++ if (queueUpdate.getPosition() != -1) { ++ fireQueuePositionEvent(queueUpdate.getPosition()); ++ } ++ if (queueUpdate.getRemaingTime() != -1) { ++ fireQueueTimeEvent(queueUpdate.getRemaingTime()); ++ } ++ } ++ ++ else { ++ // Check if a room invitation was sent and if the sender is the workgroup ++ MUCUser mucUser = (MUCUser)msg.getExtension("x", "http://jabber.org/protocol/muc#user"); ++ MUCUser.Invite invite = mucUser != null ? mucUser.getInvite() : null; ++ if (invite != null && workgroupJID.equals(invite.getFrom())) { ++ String sessionID = null; ++ Map metaData = null; ++ ++ pe = msg.getExtension(SessionID.ELEMENT_NAME, ++ SessionID.NAMESPACE); ++ if (pe != null) { ++ sessionID = ((SessionID)pe).getSessionID(); ++ } ++ ++ pe = msg.getExtension(MetaData.ELEMENT_NAME, ++ MetaData.NAMESPACE); ++ if (pe != null) { ++ metaData = ((MetaData)pe).getMetaData(); ++ } ++ ++ WorkgroupInvitation inv = new WorkgroupInvitation(connection.getUser(), msg.getFrom(), ++ workgroupJID, sessionID, msg.getBody(), ++ msg.getFrom(), metaData); ++ ++ fireInvitationEvent(inv); ++ } ++ } ++ } ++ } ++ ++ /** ++ * IQ packet to request joining the workgroup queue. ++ */ ++ private class JoinQueuePacket extends IQ { ++ ++ private String userID = null; ++ private DataForm form; ++ ++ public JoinQueuePacket(String workgroup, Form answerForm, String userID) { ++ this.userID = userID; ++ ++ setTo(workgroup); ++ setType(IQ.Type.SET); ++ ++ form = answerForm.getDataFormToSend(); ++ addExtension(form); ++ } ++ ++ public String getChildElementXML() { ++ StringBuilder buf = new StringBuilder(); ++ ++ buf.append(""); ++ buf.append(""); ++ // Add the user unique identification if the session is anonymous ++ if (connection.isAnonymous()) { ++ buf.append(new UserID(userID).toXML()); ++ } ++ ++ // Append data form text ++ buf.append(form.toXML()); ++ ++ buf.append(""); ++ ++ return buf.toString(); ++ } ++ } ++ ++ /** ++ * Returns a single chat setting based on it's identified key. ++ * ++ * @param key the key to find. ++ * @return the ChatSetting if found, otherwise false. ++ * @throws XMPPException if an error occurs while getting information from the server. ++ */ ++ public ChatSetting getChatSetting(String key) throws XMPPException { ++ ChatSettings chatSettings = getChatSettings(key, -1); ++ return chatSettings.getFirstEntry(); ++ } ++ ++ /** ++ * Returns ChatSettings based on type. ++ * ++ * @param type the type of ChatSettings to return. ++ * @return the ChatSettings of given type, otherwise null. ++ * @throws XMPPException if an error occurs while getting information from the server. ++ */ ++ public ChatSettings getChatSettings(int type) throws XMPPException { ++ return getChatSettings(null, type); ++ } ++ ++ /** ++ * Returns all ChatSettings. ++ * ++ * @return all ChatSettings of a given workgroup. ++ * @throws XMPPException if an error occurs while getting information from the server. ++ */ ++ public ChatSettings getChatSettings() throws XMPPException { ++ return getChatSettings(null, -1); ++ } ++ ++ ++ /** ++ * Asks the workgroup for it's Chat Settings. ++ * ++ * @return key specify a key to retrieve only that settings. Otherwise for all settings, key should be null. ++ * @throws XMPPException if an error occurs while getting information from the server. ++ */ ++ private ChatSettings getChatSettings(String key, int type) throws XMPPException { ++ ChatSettings request = new ChatSettings(); ++ if (key != null) { ++ request.setKey(key); ++ } ++ if (type != -1) { ++ request.setType(type); ++ } ++ request.setType(IQ.Type.GET); ++ request.setTo(workgroupJID); ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); ++ connection.sendPacket(request); ++ ++ ++ ChatSettings response = (ChatSettings)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response; ++ } ++ ++ /** ++ * The workgroup service may be configured to send email. This queries the Workgroup Service ++ * to see if the email service has been configured and is available. ++ * ++ * @return true if the email service is available, otherwise return false. ++ */ ++ public boolean isEmailAvailable() { ++ ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); ++ ++ try { ++ String workgroupService = StringUtils.parseServer(workgroupJID); ++ DiscoverInfo infoResult = discoManager.discoverInfo(workgroupService); ++ return infoResult.containsFeature("jive:email:provider"); ++ } ++ catch (XMPPException e) { ++ return false; ++ } ++ } ++ ++ /** ++ * Asks the workgroup for it's Offline Settings. ++ * ++ * @return offlineSettings the offline settings for this workgroup. ++ * @throws XMPPException if an error occurs while getting information from the server. ++ */ ++ public OfflineSettings getOfflineSettings() throws XMPPException { ++ OfflineSettings request = new OfflineSettings(); ++ request.setType(IQ.Type.GET); ++ request.setTo(workgroupJID); ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); ++ connection.sendPacket(request); ++ ++ ++ OfflineSettings response = (OfflineSettings)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response; ++ } ++ ++ /** ++ * Asks the workgroup for it's Sound Settings. ++ * ++ * @return soundSettings the sound settings for the specified workgroup. ++ * @throws XMPPException if an error occurs while getting information from the server. ++ */ ++ public SoundSettings getSoundSettings() throws XMPPException { ++ SoundSettings request = new SoundSettings(); ++ request.setType(IQ.Type.GET); ++ request.setTo(workgroupJID); ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); ++ connection.sendPacket(request); ++ ++ ++ SoundSettings response = (SoundSettings)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response; ++ } ++ ++ /** ++ * Asks the workgroup for it's Properties ++ * ++ * @return the WorkgroupProperties for the specified workgroup. ++ * @throws XMPPException if an error occurs while getting information from the server. ++ */ ++ public WorkgroupProperties getWorkgroupProperties() throws XMPPException { ++ WorkgroupProperties request = new WorkgroupProperties(); ++ request.setType(IQ.Type.GET); ++ request.setTo(workgroupJID); ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); ++ connection.sendPacket(request); ++ ++ ++ WorkgroupProperties response = (WorkgroupProperties)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response; ++ } ++ ++ /** ++ * Asks the workgroup for it's Properties ++ * ++ * @param jid the jid of the user who's information you would like the workgroup to retreive. ++ * @return the WorkgroupProperties for the specified workgroup. ++ * @throws XMPPException if an error occurs while getting information from the server. ++ */ ++ public WorkgroupProperties getWorkgroupProperties(String jid) throws XMPPException { ++ WorkgroupProperties request = new WorkgroupProperties(); ++ request.setJid(jid); ++ request.setType(IQ.Type.GET); ++ request.setTo(workgroupJID); ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); ++ connection.sendPacket(request); ++ ++ ++ WorkgroupProperties response = (WorkgroupProperties)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return response; ++ } ++ ++ ++ /** ++ * Returns the Form to use for all clients of a workgroup. It is unlikely that the server ++ * will change the form (without a restart) so it is safe to keep the returned form ++ * for future submissions. ++ * ++ * @return the Form to use for searching transcripts. ++ * @throws XMPPException if an error occurs while sending the request to the server. ++ */ ++ public Form getWorkgroupForm() throws XMPPException { ++ WorkgroupForm workgroupForm = new WorkgroupForm(); ++ workgroupForm.setType(IQ.Type.GET); ++ workgroupForm.setTo(workgroupJID); ++ ++ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(workgroupForm.getPacketID())); ++ connection.sendPacket(workgroupForm); ++ ++ WorkgroupForm response = (WorkgroupForm)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); ++ ++ // Cancel the collector. ++ collector.cancel(); ++ if (response == null) { ++ throw new XMPPException("No response from server on status set."); ++ } ++ if (response.getError() != null) { ++ throw new XMPPException(response.getError()); ++ } ++ return Form.getFormFrom(response); ++ } ++ ++ /* ++ public static void main(String args[]) throws Exception { ++ Connection con = new XMPPConnection("anteros"); ++ con.connect(); ++ con.loginAnonymously(); ++ ++ Workgroup workgroup = new Workgroup("demo@workgroup.anteros", con); ++ WorkgroupProperties props = workgroup.getWorkgroupProperties("derek@anteros.com"); ++ ++ System.out.print(props); ++ con.disconnect(); ++ } ++ */ ++ ++ ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/util/ListenerEventDispatcher.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/util/ListenerEventDispatcher.java +new file mode 100644 +index 0000000..02ddc6e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/util/ListenerEventDispatcher.java +@@ -0,0 +1,131 @@ ++/** ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.util; ++ ++import java.lang.reflect.Method; ++import java.util.*; ++ ++/** ++ * This class is a very flexible event dispatcher which implements Runnable so that it can ++ * dispatch easily from a newly created thread. The usage of this in code is more or less: ++ * create a new instance of this class, use addListenerTriplet to add as many listeners ++ * as desired to be messaged, create a new Thread using the instance of this class created ++ * as the argument to the constructor, start the new Thread instance.

      ++ * ++ * Also, this is intended to be used to message methods that either return void, or have ++ * a return which the developer using this class is uninterested in receiving. ++ * ++ * @author loki der quaeler ++ */ ++public class ListenerEventDispatcher ++ implements Runnable { ++ ++ protected transient ArrayList triplets; ++ ++ protected transient boolean hasFinishedDispatching; ++ protected transient boolean isRunning; ++ ++ public ListenerEventDispatcher () { ++ super(); ++ ++ this.triplets = new ArrayList(); ++ ++ this.hasFinishedDispatching = false; ++ this.isRunning = false; ++ } ++ ++ /** ++ * Add a listener triplet - the instance of the listener to be messaged, the Method on which ++ * the listener should be messaged, and the Object array of arguments to be supplied to the ++ * Method. No attempts are made to determine whether this triplet was already added.
      ++ * ++ * Messages are dispatched in the order in which they're added via this method; so if triplet ++ * X is added after triplet Z, then triplet Z will undergo messaging prior to triplet X.
      ++ * ++ * This method should not be called once the owning Thread instance has been started; if it ++ * is called, the triplet will not be added to the messaging queue.
      ++ * ++ * @param listenerInstance the instance of the listener to receive the associated notification ++ * @param listenerMethod the Method instance representing the method through which notification ++ * will occur ++ * @param methodArguments the arguments supplied to the notification method ++ */ ++ public void addListenerTriplet(Object listenerInstance, Method listenerMethod, ++ Object[] methodArguments) ++ { ++ if (!this.isRunning) { ++ this.triplets.add(new TripletContainer(listenerInstance, listenerMethod, ++ methodArguments)); ++ } ++ } ++ ++ /** ++ * @return whether this instance has finished dispatching its messages ++ */ ++ public boolean hasFinished() { ++ return this.hasFinishedDispatching; ++ } ++ ++ public void run() { ++ ListIterator li = null; ++ ++ this.isRunning = true; ++ ++ li = this.triplets.listIterator(); ++ while (li.hasNext()) { ++ TripletContainer tc = (TripletContainer)li.next(); ++ ++ try { ++ tc.getListenerMethod().invoke(tc.getListenerInstance(), tc.getMethodArguments()); ++ } catch (Exception e) { ++ System.err.println("Exception dispatching an event: " + e); ++ ++ e.printStackTrace(); ++ } ++ } ++ ++ this.hasFinishedDispatching = true; ++ } ++ ++ ++ protected class TripletContainer { ++ ++ protected Object listenerInstance; ++ protected Method listenerMethod; ++ protected Object[] methodArguments; ++ ++ protected TripletContainer (Object inst, Method meth, Object[] args) { ++ super(); ++ ++ this.listenerInstance = inst; ++ this.listenerMethod = meth; ++ this.methodArguments = args; ++ } ++ ++ protected Object getListenerInstance() { ++ return this.listenerInstance; ++ } ++ ++ protected Method getListenerMethod() { ++ return this.listenerMethod; ++ } ++ ++ protected Object[] getMethodArguments() { ++ return this.methodArguments; ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/util/MetaDataUtils.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/util/MetaDataUtils.java +new file mode 100644 +index 0000000..46ed793 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/util/MetaDataUtils.java +@@ -0,0 +1,111 @@ ++/** ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.util; ++ ++import org.jivesoftware.smackx.workgroup.MetaData; ++import org.jivesoftware.smack.util.StringUtils; ++import org.xmlpull.v1.XmlPullParser; ++import org.xmlpull.v1.XmlPullParserException; ++ ++import java.io.IOException; ++import java.util.*; ++ ++/** ++ * Utility class for meta-data parsing and writing. ++ * ++ * @author Matt Tucker ++ */ ++public class MetaDataUtils { ++ ++ /** ++ * Parses any available meta-data and returns it as a Map of String name/value pairs. The ++ * parser must be positioned at an opening meta-data tag, or the an empty map will be returned. ++ * ++ * @param parser the XML parser positioned at an opening meta-data tag. ++ * @return the meta-data. ++ * @throws XmlPullParserException if an error occurs while parsing the XML. ++ * @throws IOException if an error occurs while parsing the XML. ++ */ ++ public static Map parseMetaData(XmlPullParser parser) throws XmlPullParserException, IOException { ++ int eventType = parser.getEventType(); ++ ++ // If correctly positioned on an opening meta-data tag, parse meta-data. ++ if ((eventType == XmlPullParser.START_TAG) ++ && parser.getName().equals(MetaData.ELEMENT_NAME) ++ && parser.getNamespace().equals(MetaData.NAMESPACE)) { ++ Map metaData = new Hashtable(); ++ ++ eventType = parser.nextTag(); ++ ++ // Keep parsing until we've gotten to end of meta-data. ++ while ((eventType != XmlPullParser.END_TAG) ++ || (!parser.getName().equals(MetaData.ELEMENT_NAME))) { ++ String name = parser.getAttributeValue(0); ++ String value = parser.nextText(); ++ ++ if (metaData.containsKey(name)) { ++ List values = (List)metaData.get(name); ++ values.add(value); ++ } ++ else { ++ List values = new ArrayList(); ++ values.add(value); ++ metaData.put(name, values); ++ } ++ ++ eventType = parser.nextTag(); ++ } ++ ++ return metaData; ++ } ++ ++ return Collections.EMPTY_MAP; ++ } ++ ++ /** ++ * Serializes a Map of String name/value pairs into the meta-data XML format. ++ * ++ * @param metaData the Map of meta-data. ++ * @return the meta-data values in XML form. ++ */ ++ public static String serializeMetaData(Map metaData) { ++ StringBuilder buf = new StringBuilder(); ++ if (metaData != null && metaData.size() > 0) { ++ buf.append(""); ++ for (Iterator i = metaData.keySet().iterator(); i.hasNext();) { ++ Object key = i.next(); ++ Object value = metaData.get(key); ++ if (value instanceof List) { ++ List values = (List)metaData.get(key); ++ for (Iterator it = values.iterator(); it.hasNext();) { ++ String v = (String)it.next(); ++ buf.append(""); ++ buf.append(StringUtils.escapeForXML(v)); ++ buf.append(""); ++ } ++ } ++ else if (value instanceof String) { ++ buf.append(""); ++ buf.append(StringUtils.escapeForXML((String)value)); ++ buf.append(""); ++ } ++ } ++ buf.append(""); ++ } ++ return buf.toString(); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/util/ModelUtil.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/util/ModelUtil.java +new file mode 100644 +index 0000000..fa183a5 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/util/ModelUtil.java +@@ -0,0 +1,323 @@ ++/** ++ * Copyright 2003-2007 Jive Software. ++ * ++ * All rights reserved. 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.smackx.workgroup.util; ++ ++import java.util.*; ++ ++/** ++ * Utility methods frequently used by data classes and design-time ++ * classes. ++ */ ++public final class ModelUtil { ++ private ModelUtil() { ++ // Prevents instantiation. ++ } ++ ++ /** ++ * This is a utility method that compares two objects when one or ++ * both of the objects might be null The result of ++ * this method is determined as follows: ++ *

        ++ *
      1. If o1 and o2 are the same object ++ * according to the == operator, return ++ * true. ++ *
      2. Otherwise, if either o1 or o2 is ++ * null, return false. ++ *
      3. Otherwise, return o1.equals(o2). ++ *
      ++ *

      ++ * This method produces the exact logically inverted result as the ++ * {@link #areDifferent(Object, Object)} method.

      ++ *

      ++ * For array types, one of the equals methods in ++ * {@link java.util.Arrays} should be used instead of this method. ++ * Note that arrays with more than one dimension will require some ++ * custom code in order to implement equals properly. ++ */ ++ public static final boolean areEqual(Object o1, Object o2) { ++ if (o1 == o2) { ++ return true; ++ } ++ else if (o1 == null || o2 == null) { ++ return false; ++ } ++ else { ++ return o1.equals(o2); ++ } ++ } ++ ++ /** ++ * This is a utility method that compares two Booleans when one or ++ * both of the objects might be null The result of ++ * this method is determined as follows: ++ *

        ++ *
      1. If b1 and b2 are both TRUE or ++ * neither b1 nor b2 is TRUE, ++ * return true. ++ *
      2. Otherwise, return false. ++ *
      ++ *

      ++ */ ++ public static final boolean areBooleansEqual(Boolean b1, Boolean b2) { ++ // !jwetherb treat NULL the same as Boolean.FALSE ++ return (b1 == Boolean.TRUE && b2 == Boolean.TRUE) || ++ (b1 != Boolean.TRUE && b2 != Boolean.TRUE); ++ } ++ ++ /** ++ * This is a utility method that compares two objects when one or ++ * both of the objects might be null. The result ++ * returned by this method is determined as follows: ++ *

        ++ *
      1. If o1 and o2 are the same object ++ * according to the == operator, return ++ * false. ++ *
      2. Otherwise, if either o1 or o2 is ++ * null, return true. ++ *
      3. Otherwise, return !o1.equals(o2). ++ *
      ++ *

      ++ * This method produces the exact logically inverted result as the ++ * {@link #areEqual(Object, Object)} method.

      ++ *

      ++ * For array types, one of the equals methods in ++ * {@link java.util.Arrays} should be used instead of this method. ++ * Note that arrays with more than one dimension will require some ++ * custom code in order to implement equals properly. ++ */ ++ public static final boolean areDifferent(Object o1, Object o2) { ++ return !areEqual(o1, o2); ++ } ++ ++ ++ /** ++ * This is a utility method that compares two Booleans when one or ++ * both of the objects might be null The result of ++ * this method is determined as follows: ++ *

        ++ *
      1. If b1 and b2 are both TRUE or ++ * neither b1 nor b2 is TRUE, ++ * return false. ++ *
      2. Otherwise, return true. ++ *
      ++ *

      ++ * This method produces the exact logically inverted result as the ++ * {@link #areBooleansEqual(Boolean, Boolean)} method.

      ++ */ ++ public static final boolean areBooleansDifferent(Boolean b1, Boolean b2) { ++ return !areBooleansEqual(b1, b2); ++ } ++ ++ ++ /** ++ * Returns true if the specified array is not null ++ * and contains a non-null element. Returns false ++ * if the array is null or if all the array elements are null. ++ */ ++ public static final boolean hasNonNullElement(Object[] array) { ++ if (array != null) { ++ final int n = array.length; ++ for (int i = 0; i < n; i++) { ++ if (array[i] != null) { ++ return true; ++ } ++ } ++ } ++ return false; ++ } ++ ++ /** ++ * Returns a single string that is the concatenation of all the ++ * strings in the specified string array. A single space is ++ * put between each string array element. Null array elements ++ * are skipped. If the array itself is null, the empty string ++ * is returned. This method is guaranteed to return a non-null ++ * value, if no expections are thrown. ++ */ ++ public static final String concat(String[] strs) { ++ return concat(strs, " "); //NOTRANS ++ } ++ ++ /** ++ * Returns a single string that is the concatenation of all the ++ * strings in the specified string array. The strings are separated ++ * by the specified delimiter. Null array elements are skipped. If ++ * the array itself is null, the empty string is returned. This ++ * method is guaranteed to return a non-null value, if no expections ++ * are thrown. ++ */ ++ public static final String concat(String[] strs, String delim) { ++ if (strs != null) { ++ final StringBuilder buf = new StringBuilder(); ++ final int n = strs.length; ++ for (int i = 0; i < n; i++) { ++ final String str = strs[i]; ++ if (str != null) { ++ buf.append(str).append(delim); ++ } ++ } ++ final int length = buf.length(); ++ if (length > 0) { ++ // Trim trailing space. ++ buf.setLength(length - 1); ++ } ++ return buf.toString(); ++ } ++ else { ++ return ""; // NOTRANS ++ } ++ } ++ ++ /** ++ * Returns true if the specified {@link String} is not ++ * null and has a length greater than zero. This is ++ * a very frequently occurring check. ++ */ ++ public static final boolean hasLength(String s) { ++ return (s != null && s.length() > 0); ++ } ++ ++ ++ /** ++ * Returns null if the specified string is empty or ++ * null. Otherwise the string itself is returned. ++ */ ++ public static final String nullifyIfEmpty(String s) { ++ return ModelUtil.hasLength(s) ? s : null; ++ } ++ ++ /** ++ * Returns null if the specified object is null ++ * or if its toString() representation is empty. ++ * Otherwise, the toString() representation of the ++ * object itself is returned. ++ */ ++ public static final String nullifyingToString(Object o) { ++ return o != null ? nullifyIfEmpty(o.toString()) : null; ++ } ++ ++ /** ++ * Determines if a string has been changed. ++ * ++ * @param oldString is the initial value of the String ++ * @param newString is the new value of the String ++ * @return true If both oldString and newString are null or if they are ++ * both not null and equal to each other. Otherwise returns false. ++ */ ++ public static boolean hasStringChanged(String oldString, String newString) { ++ if (oldString == null && newString == null) { ++ return false; ++ } ++ else if ((oldString == null && newString != null) ++ || (oldString != null && newString == null)) { ++ return true; ++ } ++ else { ++ return !oldString.equals(newString); ++ } ++ } ++ ++ public static String getTimeFromLong(long diff) { ++ final String HOURS = "h"; ++ final String MINUTES = "min"; ++ final String SECONDS = "sec"; ++ ++ final long MS_IN_A_DAY = 1000 * 60 * 60 * 24; ++ final long MS_IN_AN_HOUR = 1000 * 60 * 60; ++ final long MS_IN_A_MINUTE = 1000 * 60; ++ final long MS_IN_A_SECOND = 1000; ++ Date currentTime = new Date(); ++ long numDays = diff / MS_IN_A_DAY; ++ diff = diff % MS_IN_A_DAY; ++ long numHours = diff / MS_IN_AN_HOUR; ++ diff = diff % MS_IN_AN_HOUR; ++ long numMinutes = diff / MS_IN_A_MINUTE; ++ diff = diff % MS_IN_A_MINUTE; ++ long numSeconds = diff / MS_IN_A_SECOND; ++ diff = diff % MS_IN_A_SECOND; ++ long numMilliseconds = diff; ++ ++ StringBuilder buf = new StringBuilder(); ++ if (numHours > 0) { ++ buf.append(numHours + " " + HOURS + ", "); ++ } ++ ++ if (numMinutes > 0) { ++ buf.append(numMinutes + " " + MINUTES + ", "); ++ } ++ ++ buf.append(numSeconds + " " + SECONDS); ++ ++ String result = buf.toString(); ++ return result; ++ } ++ ++ ++ /** ++ * Build a List of all elements in an Iterator. ++ */ ++ public static List iteratorAsList(Iterator i) { ++ ArrayList list = new ArrayList(10); ++ while (i.hasNext()) { ++ list.add(i.next()); ++ } ++ return list; ++ } ++ ++ /** ++ * Creates an Iterator that is the reverse of a ListIterator. ++ */ ++ public static Iterator reverseListIterator(ListIterator i) { ++ return new ReverseListIterator(i); ++ } ++} ++ ++/** ++ * An Iterator that is the reverse of a ListIterator. ++ */ ++class ReverseListIterator implements Iterator { ++ private ListIterator _i; ++ ++ ReverseListIterator(ListIterator i) { ++ _i = i; ++ while (_i.hasNext()) ++ _i.next(); ++ } ++ ++ public boolean hasNext() { ++ return _i.hasPrevious(); ++ } ++ ++ public Object next() { ++ return _i.previous(); ++ } ++ ++ public void remove() { ++ _i.remove(); ++ } ++} ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/external/asmack/build/src/trunk/org/jmdns/JmDNS.java b/external/asmack/build/src/trunk/org/jmdns/JmDNS.java +new file mode 100644 +index 0000000..946166f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/JmDNS.java +@@ -0,0 +1,208 @@ ++///Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++ ++package org.jmdns; ++ ++import java.io.IOException; ++import java.net.InetAddress; ++ ++import org.jmdns.impl.JmDNSImpl; ++ ++/** ++ * mDNS implementation in Java. ++ * ++ * @version %I%, %G% ++ * @author Arthur van Hoff, Rick Blair, Jeff Sonstein, ++ * Werner Randelshofer, Pierre Frisch, Scott Lewis, Scott Cytacki ++ */ ++public abstract class JmDNS ++{ ++ /** ++ * The version of JmDNS. ++ */ ++ public static String VERSION = "2.0"; ++ ++ /** ++ * Create an instance of JmDNS. ++ */ ++ public static JmDNS create() throws IOException ++ { ++ return new JmDNSImpl(); ++ } ++ ++ /** ++ * Create an instance of JmDNS and bind it to a ++ * specific network interface given its IP-address. ++ */ ++ public static JmDNS create(InetAddress addr) throws IOException ++ { ++ return new JmDNSImpl(addr); ++ } ++ ++ /** ++ * Return the HostName associated with this JmDNS instance. ++ * Note: May not be the same as what started. The host name is subject to ++ * negotiation. ++ */ ++ public abstract String getHostName(); ++ ++ /** ++ * Return the address of the interface to which this instance of JmDNS is ++ * bound. ++ */ ++ public abstract InetAddress getInterface() throws IOException; ++ ++ /** ++ * Get service information. If the information is not cached, the method ++ * will block until updated information is received. ++ *

      ++ * Usage note: Do not call this method from the AWT event dispatcher thread. ++ * You will make the user interface unresponsive. ++ * ++ * @param type fully qualified service type, such as _http._tcp.local. . ++ * @param name unqualified service name, such as foobar . ++ * @return null if the service information cannot be obtained ++ */ ++ public abstract ServiceInfo getServiceInfo(String type, String name); ++ ++ /** ++ * Get service information. If the information is not cached, the method ++ * will block for the given timeout until updated information is received. ++ *

      ++ * Usage note: If you call this method from the AWT event dispatcher thread, ++ * use a small timeout, or you will make the user interface unresponsive. ++ * ++ * @param type full qualified service type, such as _http._tcp.local. . ++ * @param name unqualified service name, such as foobar . ++ * @param timeout timeout in milliseconds ++ * @return null if the service information cannot be obtained ++ */ ++ public abstract ServiceInfo getServiceInfo(String type, String name, int timeout); ++ ++ /** ++ * Request service information. The information about the service is ++ * requested and the ServiceListener.resolveService method is called as soon ++ * as it is available. ++ *

      ++ * Usage note: Do not call this method from the AWT event dispatcher thread. ++ * You will make the user interface unresponsive. ++ * ++ * @param type full qualified service type, such as _http._tcp.local. . ++ * @param name unqualified service name, such as foobar . ++ */ ++ public abstract void requestServiceInfo(String type, String name); ++ ++ /** ++ * Request service information. The information about the service is ++ * requested and the ServiceListener.resolveService method is called as soon ++ * as it is available. ++ *

      ++ * Usage note: Do not call this method from the AWT event dispatcher thread. ++ * You will make the user interface unresponsive. ++ * ++ * @param type full qualified service type, such as _http._tcp.local. . ++ * @param name unqualified service name, such as foobar . ++ * @param persistent if true ServiceListener.resolveService will be called whenever new new information is received. ++ */ ++ public abstract void requestServiceInfo(String type, String name, boolean persistent); ++ ++ /** ++ * Request service information. The information about the service is requested ++ * and the ServiceListener.resolveService method is called as soon as it is available. ++ * ++ * @param type full qualified service type, such as _http._tcp.local. . ++ * @param name unqualified service name, such as foobar . ++ * @param timeout timeout in milliseconds ++ */ ++ public abstract void requestServiceInfo(String type, String name, int timeout); ++ ++ /** ++ * Request service information. The information about the service is requested ++ * and the ServiceListener.resolveService method is called as soon as it is available. ++ * ++ * @param type full qualified service type, such as _http._tcp.local. . ++ * @param name unqualified service name, such as foobar . ++ * @param persistent if true ServiceListener.resolveService will be called whenever new new information is received. ++ * @param timeout timeout in milliseconds ++ */ ++ public abstract void requestServiceInfo(String type, String name, boolean persistent, int timeout); ++ ++ /** ++ * Listen for service types. ++ * ++ * @param listener listener for service types ++ */ ++ public abstract void addServiceTypeListener(ServiceTypeListener listener) throws IOException; ++ ++ /** ++ * Remove listener for service types. ++ * ++ * @param listener listener for service types ++ */ ++ public abstract void removeServiceTypeListener(ServiceTypeListener listener); ++ ++ /** ++ * Listen for services of a given type. The type has to be a fully qualified ++ * type name such as _http._tcp.local.. ++ * ++ * @param type full qualified service type, such as _http._tcp.local.. ++ * @param listener listener for service updates ++ */ ++ public abstract void addServiceListener(String type, ServiceListener listener); ++ ++ /** ++ * Remove listener for services of a given type. ++ * ++ * @param listener listener for service updates ++ */ ++ public abstract void removeServiceListener(String type, ServiceListener listener); ++ ++ /** ++ * Register a service. The service is registered for access by other jmdns clients. ++ * The name of the service may be changed to make it unique. ++ */ ++ public abstract void registerService(ServiceInfo info) throws IOException; ++ ++ /** Reannounce a service. ++ */ ++ public abstract void reannounceService(ServiceInfo info) throws IOException; ++ ++ /** ++ * Unregister a service. The service should have been registered. ++ */ ++ public abstract void unregisterService(ServiceInfo info); ++ ++ /** ++ * Unregister all services. ++ */ ++ public abstract void unregisterAllServices(); ++ ++ /** ++ * Register a service type. If this service type was not already known, ++ * all service listeners will be notified of the new service type. Service types ++ * are automatically registered as they are discovered. ++ */ ++ public abstract void registerServiceType(String type); ++ ++ /** ++ * Close down jmdns. Release all resources and unregister all services. ++ */ ++ public abstract void close(); ++ ++ /** ++ * List Services and serviceTypes. ++ * Debugging Only ++ */ ++ public abstract void printServices(); ++ ++ /** ++ * Returns a list of service infos of the specified type. ++ * ++ * @param type Service type name, such as _http._tcp.local.. ++ * @return An array of service instance names. ++ */ ++ public abstract ServiceInfo[] list(String type); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/ServiceEvent.java b/external/asmack/build/src/trunk/org/jmdns/ServiceEvent.java +new file mode 100644 +index 0000000..8ad687f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/ServiceEvent.java +@@ -0,0 +1,43 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package org.jmdns; ++ ++import java.util.EventObject; ++ ++public abstract class ServiceEvent extends EventObject ++{ ++ ++ public ServiceEvent(Object source) ++ { ++ super(source); ++ // TODO Auto-generated constructor stub ++ } ++ ++ /** ++ * Returns the JmDNS instance which originated the event. ++ */ ++ public abstract JmDNS getDNS(); ++ ++ /** ++ * Returns the fully qualified type of the service. ++ */ ++ public abstract String getType(); ++ ++ /** ++ * Returns the instance name of the service. ++ * Always returns null, if the event is sent to a service type listener. ++ */ ++ public abstract String getName(); ++ ++ /** ++ * Returns the service info record, or null if the service could not be ++ * resolved. ++ * Always returns null, if the event is sent to a service type listener. ++ */ ++ /** ++ * @see org.jmdns.ServiceEvent#getInfo() ++ */ ++ public abstract ServiceInfo getInfo(); ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jmdns/ServiceInfo.java b/external/asmack/build/src/trunk/org/jmdns/ServiceInfo.java +new file mode 100644 +index 0000000..5a71ceb +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/ServiceInfo.java +@@ -0,0 +1,196 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++package org.jmdns; ++ ++import java.net.InetAddress; ++import java.util.Enumeration; ++import java.util.Hashtable; ++ ++import org.jmdns.impl.ServiceInfoImpl; ++ ++public abstract class ServiceInfo ++{ ++ public final static byte[] NO_VALUE = new byte[0]; ++ ++ /** ++ * Construct a service description for registrating with JmDNS. ++ * ++ * @param type fully qualified service type name, such as _http._tcp.local.. ++ * @param name unqualified service instance name, such as foobar ++ * @param port the local port on which the service runs ++ * @param text string describing the service ++ */ ++ public static ServiceInfo create(String type, String name, int port, String text) ++ { ++ return new ServiceInfoImpl(type, name, port, text); ++ } ++ ++ /** ++ * Construct a service description for registrating with JmDNS. ++ * ++ * @param type fully qualified service type name, such as _http._tcp.local.. ++ * @param name unqualified service instance name, such as foobar ++ * @param port the local port on which the service runs ++ * @param weight weight of the service ++ * @param priority priority of the service ++ * @param text string describing the service ++ */ ++ public static ServiceInfo create(String type, String name, int port, int weight, int priority, String text) ++ { ++ return new ServiceInfoImpl(type, name, port, weight, priority, text); ++ } ++ ++ /** ++ * Construct a service description for registrating with JmDNS. The properties hashtable must ++ * map property names to either Strings or byte arrays describing the property values. ++ * ++ * @param type fully qualified service type name, such as _http._tcp.local.. ++ * @param name unqualified service instance name, such as foobar ++ * @param port the local port on which the service runs ++ * @param weight weight of the service ++ * @param priority priority of the service ++ * @param props properties describing the service ++ */ ++ public static ServiceInfo create(String type, String name, int port, int weight, int priority, Hashtable props) ++ { ++ return new ServiceInfoImpl(type, name, port, weight, priority, props); ++ } ++ ++ /** ++ * Construct a service description for registrating with JmDNS. ++ * ++ * @param type fully qualified service type name, such as _http._tcp.local.. ++ * @param name unqualified service instance name, such as foobar ++ * @param port the local port on which the service runs ++ * @param weight weight of the service ++ * @param priority priority of the service ++ * @param text bytes describing the service ++ */ ++ public static ServiceInfo create(String type, String name, int port, int weight, int priority, byte text[]) ++ { ++ return new ServiceInfoImpl(type, name, port, weight, priority, text); ++ } ++ ++ /** ++ * Fully qualified service type name, such as _http._tcp.local. . ++ */ ++ public abstract String getType(); ++ ++ /** ++ * Unqualified service instance name, such as foobar . ++ */ ++ public abstract String getName(); ++ ++ /** ++ * Fully qualified service name, such as foobar._http._tcp.local. . ++ */ ++ public abstract String getQualifiedName(); ++ ++ /** ++ * Get the name of the server. ++ */ ++ public abstract String getServer(); ++ ++ /** ++ * Get the host address of the service (ie X.X.X.X). ++ */ ++ public abstract String getHostAddress(); ++ ++ public abstract InetAddress getAddress(); ++ ++ /** ++ * Get the InetAddress of the service. ++ */ ++ public abstract InetAddress getInetAddress(); ++ ++ /** ++ * Get the port for the service. ++ */ ++ public abstract int getPort(); ++ ++ /** ++ * Get the priority of the service. ++ */ ++ public abstract int getPriority(); ++ ++ /** ++ * Get the weight of the service. ++ */ ++ public abstract int getWeight(); ++ ++ /** ++ * Get the text for the serivce as raw bytes. ++ */ ++ public abstract byte[] getTextBytes(); ++ ++ /** ++ * Get the text for the service. This will interpret the text bytes ++ * as a UTF8 encoded string. Will return null if the bytes are not ++ * a valid UTF8 encoded string. ++ */ ++ public abstract String getTextString(); ++ ++ /** ++ * Get the URL for this service. An http URL is created by ++ * combining the address, port, and path properties. ++ */ ++ public abstract String getURL(); ++ ++ /** ++ * Get the URL for this service. An URL is created by ++ * combining the protocol, address, port, and path properties. ++ */ ++ public abstract String getURL(String protocol); ++ ++ /** ++ * Get a property of the service. This involves decoding the ++ * text bytes into a property list. Returns null if the property ++ * is not found or the text data could not be decoded correctly. ++ */ ++ public abstract byte[] getPropertyBytes(String name); ++ ++ /** ++ * Get a property of the service. This involves decoding the ++ * text bytes into a property list. Returns null if the property ++ * is not found, the text data could not be decoded correctly, or ++ * the resulting bytes are not a valid UTF8 string. ++ */ ++ public abstract String getPropertyString(String name); ++ ++ /** ++ * Enumeration of the property names. ++ */ ++ public abstract Enumeration getPropertyNames(); ++ ++ public abstract String getNiceTextString(); ++ ++ /** ++ * Set the text for the service. ++ * ++ * @param text the raw byte representation of the text field. ++ */ ++ public abstract void setText(byte [] text); ++ ++ /** ++ * Set the text for the service. ++ * ++ * @param props a key=value map that will be encoded into raw bytes. ++ */ ++ public abstract void setText(Hashtable props); ++ ++ /** ++ * Add a service name listener. A service name listener is notified when ++ * the service name of a service is changed. ++ * ++ * @param listener the listener to be added. ++ */ ++ public abstract void addServiceNameListener(ServiceNameListener listener); ++ ++ /** ++ * Remove a service name listener. ++ * ++ * @param listener the listener to be removed ++ */ ++ public abstract void removeServiceNameListener(ServiceNameListener listener); ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/ServiceListener.java b/external/asmack/build/src/trunk/org/jmdns/ServiceListener.java +new file mode 100644 +index 0000000..fb45385 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/ServiceListener.java +@@ -0,0 +1,42 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package org.jmdns; ++ ++import java.util.EventListener; ++ ++/** ++ * Listener for service updates. ++ * ++ * @version %I%, %G% ++ * @author Arthur van Hoff, Werner Randelshofer ++ */ ++public interface ServiceListener extends EventListener ++{ ++ /** ++ * A service has been added. ++ * ++ * @param event The ServiceEvent providing the name and fully qualified type ++ * of the service. ++ */ ++ void serviceAdded(ServiceEvent event); ++ ++ /** ++ * A service has been removed. ++ * ++ * @param event The ServiceEvent providing the name and fully qualified type ++ * of the service. ++ */ ++ void serviceRemoved(ServiceEvent event); ++ ++ /** ++ * A service has been resolved. Its details are now available in the ++ * ServiceInfo record. ++ * ++ * @param event The ServiceEvent providing the name, the fully qualified ++ * type of the service, and the service info record, or null if the service ++ * could not be resolved. ++ */ ++ void serviceResolved(ServiceEvent event); ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/ServiceNameListener.java b/external/asmack/build/src/trunk/org/jmdns/ServiceNameListener.java +new file mode 100644 +index 0000000..4bd6fc3 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/ServiceNameListener.java +@@ -0,0 +1,16 @@ ++//Copyright 2009 Jonas Ã…dahl ++//Licensed under Apache License version 2.0 ++ ++package org.jmdns; ++ ++/** ++ * Listener class used for notifications about service names of service ++ * being changed. ++ */ ++public interface ServiceNameListener { ++ ++ /** ++ * Called when the name of a service info has been changed. ++ */ ++ public void serviceNameChanged(String newName, String oldName); ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/ServiceTypeListener.java b/external/asmack/build/src/trunk/org/jmdns/ServiceTypeListener.java +new file mode 100644 +index 0000000..f4bccda +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/ServiceTypeListener.java +@@ -0,0 +1,24 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package org.jmdns; ++ ++import java.util.EventListener; ++ ++/** ++ * Listener for service types. ++ * ++ * @version %I%, %G% ++ * @author Arthur van Hoff, Werner Randelshofer ++ */ ++public interface ServiceTypeListener extends EventListener ++{ ++ /** ++ * A new service type was discovered. ++ * ++ * @param event The service event providing the fully qualified type of ++ * the service. ++ */ ++ void serviceTypeAdded(ServiceEvent event); ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/DNSCache.java b/external/asmack/build/src/trunk/org/jmdns/impl/DNSCache.java +new file mode 100644 +index 0000000..191e2de +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/DNSCache.java +@@ -0,0 +1,252 @@ ++//Copyright 2003-2005 Arthur van Hoff Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package org.jmdns.impl; ++ ++import java.util.ArrayList; ++import java.util.HashMap; ++import java.util.Iterator; ++import java.util.logging.Logger; ++ ++/** ++ * A table of DNS entries. This is a hash table which can handle multiple ++ * entries with the same name.

      Storing multiple entries with the same name ++ * is implemented using a linked list of CacheNode's.

      The ++ * current implementation of the API of DNSCache does expose the cache nodes to ++ * clients. Clients must explicitly deal with the nodes when iterating over ++ * entries in the cache. Here's how to iterate over all entries in the cache: ++ * ++ *

      ++ * for (Iterator i=dnscache.iterator(); i.hasNext(); ) {
      ++ *    for (DNSCache.CacheNode n = (DNSCache.CacheNode) i.next(); n != null; n.next()) {
      ++ *       DNSEntry entry = n.getValue();
      ++ *       ...do something with entry...
      ++ *    }
      ++ * }
      ++ * 
      ++ * ++ *

      And here's how to iterate over all entries having a given name: ++ * ++ *

      ++ * for (DNSCache.CacheNode n = (DNSCache.CacheNode) dnscache.find(name); n != null; n.next()) {
      ++ *     DNSEntry entry = n.getValue();
      ++ *     ...do something with entry...
      ++ * }
      ++ * 
      ++ * ++ * @version %I%, %G% ++ * @author Arthur van Hoff, Werner Randelshofer, Rick Blair ++ */ ++public class DNSCache ++{ ++ private static Logger logger = Logger.getLogger(DNSCache.class.getName()); ++ // Implementation note: ++ // We might completely hide the existence of CacheNode's in a future version ++ // of DNSCache. But this will require to implement two (inner) classes for ++ // the iterators that will be returned by method iterator() and ++ // method find(name). ++ // Since DNSCache is not a public class, it does not seem worth the effort ++ // to clean its API up that much. ++ ++ // [PJYF Oct 15 2004] This should implements Collections that would be amuch ++ // cleaner implementation ++ ++ /** ++ * The number of DNSEntry's in the cache. ++ */ ++ private int size; ++ ++ /** ++ * The hashtable used internally to store the entries of the cache. Keys are ++ * instances of String. The String contains an unqualified service name. ++ * Values are linked lists of CacheNode instances. ++ */ ++ private final HashMap hashtable; ++ ++ /** ++ * Cache nodes are used to implement storage of multiple DNSEntry's of the ++ * same name in the cache. ++ */ ++ public static class CacheNode ++ { ++ private static Logger logger = Logger.getLogger(CacheNode.class.getName()); ++ private final DNSEntry value; ++ private CacheNode next; ++ ++ public CacheNode(DNSEntry value) ++ { ++ this.value = value; ++ } ++ ++ public CacheNode next() ++ { ++ return next; ++ } ++ ++ public DNSEntry getValue() ++ { ++ return value; ++ } ++ } ++ ++ /** ++ * Create a table with a given initial size. ++ */ ++ public DNSCache(final int size) ++ { ++ hashtable = new HashMap(size); ++ } ++ ++ /** ++ * Clears the cache. ++ */ ++ public synchronized void clear() ++ { ++ hashtable.clear(); ++ size = 0; ++ } ++ ++ /** ++ * Adds an entry to the table. ++ */ ++ public synchronized void add(final DNSEntry entry) ++ { ++ // logger.log("DNSCache.add("+entry.getName()+")"); ++ final CacheNode newValue = new CacheNode(entry); ++ final CacheNode node = (CacheNode) hashtable.get(entry.getName()); ++ if (node == null) ++ { ++ hashtable.put(entry.getName(), newValue); ++ } ++ else ++ { ++ newValue.next = node.next; ++ node.next = newValue; ++ } ++ size++; ++ } ++ ++ /** ++ * Remove a specific entry from the table. Returns true if the entry was ++ * found. ++ */ ++ public synchronized boolean remove(DNSEntry entry) ++ { ++ CacheNode node = (CacheNode) hashtable.get(entry.getName()); ++ if (node != null) ++ { ++ if (node.value == entry) ++ { ++ if (node.next == null) ++ { ++ hashtable.remove(entry.getName()); ++ } ++ else ++ { ++ hashtable.put(entry.getName(), node.next); ++ } ++ size--; ++ return true; ++ } ++ ++ CacheNode previous = node; ++ node = node.next; ++ while (node != null) ++ { ++ if (node.value == entry) ++ { ++ previous.next = node.next; ++ size--; ++ return true; ++ } ++ previous = node; ++ node = node.next; ++ } ++ ; ++ } ++ return false; ++ } ++ ++ /** ++ * Get a matching DNS entry from the table (using equals). Returns the entry ++ * that was found. ++ */ ++ public synchronized DNSEntry get(DNSEntry entry) ++ { ++ for (CacheNode node = find(entry.getName()); node != null; node = node.next) ++ { ++ if (node.value.equals(entry)) ++ { ++ return node.value; ++ } ++ } ++ return null; ++ } ++ ++ /** ++ * Get a matching DNS entry from the table. ++ */ ++ public synchronized DNSEntry get(String name, int type, int clazz) ++ { ++ for (CacheNode node = find(name); node != null; node = node.next) ++ { ++ if (node.value.type == type && node.value.clazz == clazz) ++ { ++ return node.value; ++ } ++ } ++ return null; ++ } ++ ++ /** ++ * Iterates over all cache nodes. The iterator returns instances of ++ * DNSCache.CacheNode. Each instance returned is the first node of a linked ++ * list. To retrieve all entries, one must iterate over this linked list. ++ * See code snippets in the header of the class. ++ */ ++ public synchronized Iterator iterator() ++ { ++ return new ArrayList(hashtable.values()).iterator(); ++ } ++ ++ /** ++ * Iterate only over items with matching name. Returns an instance of ++ * DNSCache.CacheNode or null. If an instance is returned, it is the first ++ * node of a linked list. To retrieve all entries, one must iterate over ++ * this linked list. ++ */ ++ public synchronized CacheNode find(String name) ++ { ++ return (CacheNode) hashtable.get(name); ++ } ++ ++ /** ++ * List all entries for debugging. ++ */ ++ public synchronized void print() ++ { ++ for (final Iterator i = iterator(); i.hasNext();) ++ { ++ for (CacheNode n = (CacheNode) i.next(); n != null; n = n.next) ++ { ++ System.out.println(n.value); ++ } ++ } ++ } ++ ++ public synchronized String toString() ++ { ++ final StringBuffer aLog = new StringBuffer(); ++ aLog.append("\t---- cache ----"); ++ for (final Iterator i = iterator(); i.hasNext();) ++ { ++ for (CacheNode n = (CacheNode) i.next(); n != null; n = n.next) ++ { ++ aLog.append("\n\t\t" + n.value); ++ } ++ } ++ return aLog.toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/DNSConstants.java b/external/asmack/build/src/trunk/org/jmdns/impl/DNSConstants.java +new file mode 100644 +index 0000000..f4c3799 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/DNSConstants.java +@@ -0,0 +1,125 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++ ++package org.jmdns.impl; ++ ++/** ++ * DNS constants. ++ * ++ * @version %I%, %G% ++ * @author Arthur van Hoff, Jeff Sonstein, Werner Randelshofer, Pierre Frisch, Rick Blair ++ */ ++public final class DNSConstants ++{ ++ ++ // changed to final class - jeffs ++ public final static String MDNS_GROUP = "224.0.0.251"; ++ public final static String MDNS_GROUP_IPV6 = "FF02::FB"; ++ public final static int MDNS_PORT = 5353; ++ public final static int DNS_PORT = 53; ++ public final static int DNS_TTL = 60 * 60; // default one hour TTL ++ // public final static int DNS_TTL = 120 * 60; // two hour TTL (draft-cheshire-dnsext-multicastdns.txt ch 13) ++ ++ public final static int MAX_MSG_TYPICAL = 1460; ++ public final static int MAX_MSG_ABSOLUTE = 8972; ++ ++ public final static int FLAGS_QR_MASK = 0x8000; // Query response mask ++ public final static int FLAGS_QR_QUERY = 0x0000; // Query ++ public final static int FLAGS_QR_RESPONSE = 0x8000; // Response ++ ++ public final static int FLAGS_AA = 0x0400; // Authorative answer ++ public final static int FLAGS_TC = 0x0200; // Truncated ++ public final static int FLAGS_RD = 0x0100; // Recursion desired ++ public final static int FLAGS_RA = 0x8000; // Recursion available ++ ++ public final static int FLAGS_Z = 0x0040; // Zero ++ public final static int FLAGS_AD = 0x0020; // Authentic data ++ public final static int FLAGS_CD = 0x0010; // Checking disabled ++ ++ public final static int CLASS_IN = 1; // public final static Internet ++ public final static int CLASS_CS = 2; // CSNET ++ public final static int CLASS_CH = 3; // CHAOS ++ public final static int CLASS_HS = 4; // Hesiod ++ public final static int CLASS_NONE = 254; // Used in DNS UPDATE [RFC 2136] ++ public final static int CLASS_ANY = 255; // Not a DNS class, but a DNS query class, meaning "all classes" ++ public final static int CLASS_MASK = 0x7FFF; // Multicast DNS uses the bottom 15 bits to identify the record class... ++ public final static int CLASS_UNIQUE = 0x8000; // ... and the top bit indicates that all other cached records are now invalid ++ ++ public final static int TYPE_IGNORE = 0; // This is a hack to stop further processing ++ public final static int TYPE_A = 1; // Address ++ public final static int TYPE_NS = 2; // Name Server ++ public final static int TYPE_MD = 3; // Mail Destination ++ public final static int TYPE_MF = 4; // Mail Forwarder ++ public final static int TYPE_CNAME = 5; // Canonical Name ++ public final static int TYPE_SOA = 6; // Start of Authority ++ public final static int TYPE_MB = 7; // Mailbox ++ public final static int TYPE_MG = 8; // Mail Group ++ public final static int TYPE_MR = 9; // Mail Rename ++ public final static int TYPE_NULL = 10; // NULL RR ++ public final static int TYPE_WKS = 11; // Well-known-service ++ public final static int TYPE_PTR = 12; // Domain Name popublic final static inter ++ public final static int TYPE_HINFO = 13; // Host information ++ public final static int TYPE_MINFO = 14; // Mailbox information ++ public final static int TYPE_MX = 15; // Mail exchanger ++ public final static int TYPE_TXT = 16; // Arbitrary text string ++ public final static int TYPE_RP = 17; // for Responsible Person [RFC1183] ++ public final static int TYPE_AFSDB = 18; // for AFS Data Base location [RFC1183] ++ public final static int TYPE_X25 = 19; // for X.25 PSDN address [RFC1183] ++ public final static int TYPE_ISDN = 20; // for ISDN address [RFC1183] ++ public final static int TYPE_RT = 21; // for Route Through [RFC1183] ++ public final static int TYPE_NSAP = 22; // for NSAP address, NSAP style A record [RFC1706] ++ public final static int TYPE_NSAP_PTR = 23; // ++ public final static int TYPE_SIG = 24; // for security signature [RFC2931] ++ public final static int TYPE_KEY = 25; // for security key [RFC2535] ++ public final static int TYPE_PX = 26; // X.400 mail mapping information [RFC2163] ++ public final static int TYPE_GPOS = 27; // Geographical Position [RFC1712] ++ public final static int TYPE_AAAA = 28; // IP6 Address [Thomson] ++ public final static int TYPE_LOC = 29; // Location Information [Vixie] ++ public final static int TYPE_NXT = 30; // Next Domain - OBSOLETE [RFC2535, RFC3755] ++ public final static int TYPE_EID = 31; // Endpoint Identifier [Patton] ++ public final static int TYPE_NIMLOC = 32; // Nimrod Locator [Patton] ++ public final static int TYPE_SRV = 33; // Server Selection [RFC2782] ++ public final static int TYPE_ATMA = 34; // ATM Address [Dobrowski] ++ public final static int TYPE_NAPTR = 35; // Naming Authority Pointer [RFC2168, RFC2915] ++ public final static int TYPE_KX = 36; // Key Exchanger [RFC2230] ++ public final static int TYPE_CERT = 37; // CERT [RFC2538] ++ public final static int TYPE_A6 = 38; // A6 [RFC2874] ++ public final static int TYPE_DNAME = 39; // DNAME [RFC2672] ++ public final static int TYPE_SINK = 40; // SINK [Eastlake] ++ public final static int TYPE_OPT = 41; // OPT [RFC2671] ++ public final static int TYPE_APL = 42; // APL [RFC3123] ++ public final static int TYPE_DS = 43; // Delegation Signer [RFC3658] ++ public final static int TYPE_SSHFP = 44; // SSH Key Fingerprint [RFC-ietf-secsh-dns-05.txt] ++ public final static int TYPE_RRSIG = 46; // RRSIG [RFC3755] ++ public final static int TYPE_NSEC = 47; // NSEC [RFC3755] ++ public final static int TYPE_DNSKEY = 48; // DNSKEY [RFC3755] ++ public final static int TYPE_UINFO = 100; // [IANA-Reserved] ++ public final static int TYPE_UID = 101; // [IANA-Reserved] ++ public final static int TYPE_GID = 102; // [IANA-Reserved] ++ public final static int TYPE_UNSPEC = 103; // [IANA-Reserved] ++ public final static int TYPE_TKEY = 249; // Transaction Key [RFC2930] ++ public final static int TYPE_TSIG = 250; // Transaction Signature [RFC2845] ++ public final static int TYPE_IXFR = 251; // Incremental transfer [RFC1995] ++ public final static int TYPE_AXFR = 252; // Transfer of an entire zone [RFC1035] ++ public final static int TYPE_MAILA = 253; // Mailbox-related records (MB, MG or MR) [RFC1035] ++ public final static int TYPE_MAILB = 254; // Mail agent RRs (Obsolete - see MX) [RFC1035] ++ public final static int TYPE_ANY = 255; // Request for all records [RFC1035] ++ ++ //Time Intervals for various functions ++ ++ public final static int SHARED_QUERY_TIME = 20; //milliseconds before send shared query ++ public final static int QUERY_WAIT_INTERVAL = 225; //milliseconds between query loops. ++ public final static int PROBE_WAIT_INTERVAL = 250; //milliseconds between probe loops. ++ public final static int RESPONSE_MIN_WAIT_INTERVAL = 20; //minimal wait interval for response. ++ public final static int RESPONSE_MAX_WAIT_INTERVAL = 115; //maximal wait interval for response ++ public final static int PROBE_CONFLICT_INTERVAL = 1000; //milliseconds to wait after conflict. ++ public final static int PROBE_THROTTLE_COUNT = 10; //After x tries go 1 time a sec. on probes. ++ public final static int PROBE_THROTTLE_COUNT_INTERVAL = 5000; //We only increment the throttle count, if ++ // the previous increment is inside this interval. ++ public final static int ANNOUNCE_WAIT_INTERVAL = 1000; //milliseconds between Announce loops. ++ public final static int RECORD_REAPER_INTERVAL = 10000; //milliseconds between cache cleanups. ++ public final static int KNOWN_ANSWER_TTL = 120; ++ public final static int ANNOUNCED_RENEWAL_TTL_INTERVAL = DNS_TTL * 500; // 50% of the TTL in milliseconds ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/DNSEntry.java b/external/asmack/build/src/trunk/org/jmdns/impl/DNSEntry.java +new file mode 100644 +index 0000000..617439f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/DNSEntry.java +@@ -0,0 +1,148 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++ ++package org.jmdns.impl; ++ ++import java.util.logging.Logger; ++ ++/** ++ * DNS entry with a name, type, and class. This is the base ++ * class for questions and records. ++ * ++ * @version %I%, %G% ++ * @author Arthur van Hoff, Pierre Frisch, Rick Blair ++ */ ++public class DNSEntry ++{ ++ private static Logger logger = Logger.getLogger(DNSEntry.class.getName()); ++ String key; ++ String name; ++ int type; ++ int clazz; ++ boolean unique; ++ ++ /** ++ * Create an entry. ++ */ ++ DNSEntry(String name, int type, int clazz) ++ { ++ this.key = name.toLowerCase(); ++ this.name = name; ++ this.type = type; ++ this.clazz = clazz & DNSConstants.CLASS_MASK; ++ this.unique = (clazz & DNSConstants.CLASS_UNIQUE) != 0; ++ } ++ ++ /** ++ * Check if two entries have exactly the same name, type, and class. ++ */ ++ public boolean equals(Object obj) ++ { ++ if (obj instanceof DNSEntry) ++ { ++ DNSEntry other = (DNSEntry) obj; ++ return name.equals(other.name) && type == other.type && clazz == other.clazz; ++ } ++ return false; ++ } ++ ++ public String getName() ++ { ++ return name; ++ } ++ ++ public int getType() ++ { ++ return type; ++ } ++ ++ /** ++ * Overriden, to return a value which is consistent with the value returned ++ * by equals(Object). ++ */ ++ public int hashCode() ++ { ++ return name.hashCode() + type + clazz; ++ } ++ ++ /** ++ * Get a string given a clazz. ++ */ ++ static String getClazz(int clazz) ++ { ++ switch (clazz & DNSConstants.CLASS_MASK) ++ { ++ case DNSConstants.CLASS_IN: ++ return "in"; ++ case DNSConstants.CLASS_CS: ++ return "cs"; ++ case DNSConstants.CLASS_CH: ++ return "ch"; ++ case DNSConstants.CLASS_HS: ++ return "hs"; ++ case DNSConstants.CLASS_NONE: ++ return "none"; ++ case DNSConstants.CLASS_ANY: ++ return "any"; ++ default: ++ return "?"; ++ } ++ } ++ ++ /** ++ * Get a string given a type. ++ */ ++ static String getType(int type) ++ { ++ switch (type) ++ { ++ case DNSConstants.TYPE_A: ++ return "a"; ++ case DNSConstants.TYPE_AAAA: ++ return "aaaa"; ++ case DNSConstants.TYPE_NS: ++ return "ns"; ++ case DNSConstants.TYPE_MD: ++ return "md"; ++ case DNSConstants.TYPE_MF: ++ return "mf"; ++ case DNSConstants.TYPE_CNAME: ++ return "cname"; ++ case DNSConstants.TYPE_SOA: ++ return "soa"; ++ case DNSConstants.TYPE_MB: ++ return "mb"; ++ case DNSConstants.TYPE_MG: ++ return "mg"; ++ case DNSConstants.TYPE_MR: ++ return "mr"; ++ case DNSConstants.TYPE_NULL: ++ return "null"; ++ case DNSConstants.TYPE_WKS: ++ return "wks"; ++ case DNSConstants.TYPE_PTR: ++ return "ptr"; ++ case DNSConstants.TYPE_HINFO: ++ return "hinfo"; ++ case DNSConstants.TYPE_MINFO: ++ return "minfo"; ++ case DNSConstants.TYPE_MX: ++ return "mx"; ++ case DNSConstants.TYPE_TXT: ++ return "txt"; ++ case DNSConstants.TYPE_SRV: ++ return "srv"; ++ case DNSConstants.TYPE_ANY: ++ return "any"; ++ default: ++ return "?"; ++ } ++ } ++ ++ public String toString(String hdr, String other) ++ { ++ return hdr + "[" + getType(type) + "," + getClazz(clazz) + (unique ? "-unique," : ",") + name + ((other != null) ? "," + other + "]" : "]"); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/DNSIncoming.java b/external/asmack/build/src/trunk/org/jmdns/impl/DNSIncoming.java +new file mode 100644 +index 0000000..1055850 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/DNSIncoming.java +@@ -0,0 +1,530 @@ ++///Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++ ++ ++package org.jmdns.impl; ++ ++import java.io.IOException; ++import java.net.DatagramPacket; ++import java.net.InetAddress; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++/** ++ * Parse an incoming DNS message into its components. ++ * ++ * @version %I%, %G% ++ * @author Arthur van Hoff, Werner Randelshofer, Pierre Frisch, Daniel Bobbert ++ */ ++public final class DNSIncoming ++{ ++ private static Logger logger = Logger.getLogger(DNSIncoming.class.getName()); ++ ++ // This is a hack to handle a bug in the BonjourConformanceTest ++ // It is sending out target strings that don't follow the "domain name" ++ // format. ++ public static boolean USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET = true; ++ ++ // Implementation note: This vector should be immutable. ++ // If a client of DNSIncoming changes the contents of this vector, ++ // we get undesired results. To fix this, we have to migrate to ++ // the Collections API of Java 1.2. i.e we replace Vector by List. ++ // final static Vector EMPTY = new Vector(); ++ ++ private DatagramPacket packet; ++ private int off; ++ private int len; ++ private byte data[]; ++ ++ int id; ++ private int flags; ++ private int numQuestions; ++ int numAnswers; ++ private int numAuthorities; ++ private int numAdditionals; ++ private long receivedTime; ++ ++ private List questions; ++ List answers; ++ ++ /** ++ * Parse a message from a datagram packet. ++ */ ++ DNSIncoming(DatagramPacket packet) throws IOException ++ { ++ this.packet = packet; ++ InetAddress source = packet.getAddress(); ++ this.data = packet.getData(); ++ this.len = packet.getLength(); ++ this.off = packet.getOffset(); ++ this.questions = Collections.EMPTY_LIST; ++ this.answers = Collections.EMPTY_LIST; ++ this.receivedTime = System.currentTimeMillis(); ++ ++ try ++ { ++ id = readUnsignedShort(); ++ flags = readUnsignedShort(); ++ numQuestions = readUnsignedShort(); ++ numAnswers = readUnsignedShort(); ++ numAuthorities = readUnsignedShort(); ++ numAdditionals = readUnsignedShort(); ++ ++ // parse questions ++ if (numQuestions > 0) ++ { ++ questions = Collections.synchronizedList(new ArrayList(numQuestions)); ++ for (int i = 0; i < numQuestions; i++) ++ { ++ DNSQuestion question = new DNSQuestion(readName(), readUnsignedShort(), readUnsignedShort()); ++ questions.add(question); ++ } ++ } ++ ++ // parse answers ++ int n = numAnswers + numAuthorities + numAdditionals; ++ if (n > 0) ++ { ++ answers = Collections.synchronizedList(new ArrayList(n)); ++ for (int i = 0; i < n; i++) ++ { ++ String domain = readName(); ++ int type = readUnsignedShort(); ++ int clazz = readUnsignedShort(); ++ int ttl = readInt(); ++ int len = readUnsignedShort(); ++ int end = off + len; ++ DNSRecord rec = null; ++ ++ switch (type) ++ { ++ case DNSConstants.TYPE_A: // IPv4 ++ case DNSConstants.TYPE_AAAA: // IPv6 FIXME [PJYF Oct 14 2004] This has not been tested ++ rec = new DNSRecord.Address(domain, type, clazz, ttl, readBytes(off, len)); ++ break; ++ case DNSConstants.TYPE_CNAME: ++ case DNSConstants.TYPE_PTR: ++ String service = ""; ++ try { ++ service = readName(); ++ } catch (IOException e){ ++ // there was a problem reading the service name ++ e.printStackTrace(); ++ } ++ rec = new DNSRecord.Pointer(domain, type, clazz, ttl, service); ++ break; ++ case DNSConstants.TYPE_TXT: ++ rec = new DNSRecord.Text(domain, type, clazz, ttl, readBytes(off, len)); ++ break; ++ case DNSConstants.TYPE_SRV: ++ int priority = readUnsignedShort(); ++ int weight = readUnsignedShort(); ++ int port = readUnsignedShort(); ++ String target = ""; ++ try { ++ // This is a hack to handle a bug in the BonjourConformanceTest ++ // It is sending out target strings that don't follow the "domain name" ++ // format. ++ ++ if(USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET){ ++ target = readName(); ++ } else { ++ target = readNonNameString(); ++ } ++ } catch (IOException e) { ++ // this can happen if the type of the label ++ // cannot be handled. ++ // down below the offset gets advanced to the end ++ // of the record ++ e.printStackTrace(); ++ } ++ rec = new DNSRecord.Service(domain, type, clazz, ttl, ++ priority, weight, port, target); ++ break; ++ case DNSConstants.TYPE_HINFO: ++ // Maybe we should do something with those ++ break; ++ default : ++ logger.finer("DNSIncoming() unknown type:" + type); ++ break; ++ } ++ ++ if (rec != null) ++ { ++ rec.setRecordSource(source); ++ // Add a record, if we were able to create one. ++ answers.add(rec); ++ } ++ else ++ { ++ // Addjust the numbers for the skipped record ++ if (answers.size() < numAnswers) ++ { ++ numAnswers--; ++ } ++ else ++ { ++ if (answers.size() < numAnswers + numAuthorities) ++ { ++ numAuthorities--; ++ } ++ else ++ { ++ if (answers.size() < numAnswers + numAuthorities + numAdditionals) ++ { ++ numAdditionals--; ++ } ++ } ++ } ++ } ++ off = end; ++ } ++ } ++ } ++ catch (IOException e) ++ { ++ logger.log(Level.WARNING, "DNSIncoming() dump " + print(true) + "\n exception ", e); ++ throw e; ++ } ++ } ++ ++ /** ++ * Check if the message is a query. ++ */ ++ boolean isQuery() ++ { ++ return (flags & DNSConstants.FLAGS_QR_MASK) == DNSConstants.FLAGS_QR_QUERY; ++ } ++ ++ /** ++ * Check if the message is truncated. ++ */ ++ public boolean isTruncated() ++ { ++ return (flags & DNSConstants.FLAGS_TC) != 0; ++ } ++ ++ /** ++ * Check if the message is a response. ++ */ ++ boolean isResponse() ++ { ++ return (flags & DNSConstants.FLAGS_QR_MASK) == DNSConstants.FLAGS_QR_RESPONSE; ++ } ++ ++ private int get(int off) throws IOException ++ { ++ if ((off < 0) || (off >= len)) ++ { ++ throw new IOException("parser error: offset=" + off); ++ } ++ return data[off] & 0xFF; ++ } ++ ++ private int readUnsignedShort() throws IOException ++ { ++ return (get(off++) << 8) + get(off++); ++ } ++ ++ private int readInt() throws IOException ++ { ++ return (readUnsignedShort() << 16) + readUnsignedShort(); ++ } ++ ++ private byte[] readBytes(int off, int len) throws IOException ++ { ++ byte bytes[] = new byte[len]; ++ System.arraycopy(data, off, bytes, 0, len); ++ return bytes; ++ } ++ ++ private void readUTF(StringBuffer buf, int off, int len) throws IOException ++ { ++ for (int end = off + len; off < end;) ++ { ++ int ch = get(off++); ++ switch (ch >> 4) ++ { ++ case 0: ++ case 1: ++ case 2: ++ case 3: ++ case 4: ++ case 5: ++ case 6: ++ case 7: ++ // 0xxxxxxx ++ break; ++ case 12: ++ case 13: ++ // 110x xxxx 10xx xxxx ++ ch = ((ch & 0x1F) << 6) | (get(off++) & 0x3F); ++ break; ++ case 14: ++ // 1110 xxxx 10xx xxxx 10xx xxxx ++ ch = ((ch & 0x0f) << 12) | ((get(off++) & 0x3F) << 6) | (get(off++) & 0x3F); ++ break; ++ default: ++ // 10xx xxxx, 1111 xxxx ++ ch = ((ch & 0x3F) << 4) | (get(off++) & 0x0f); ++ break; ++ } ++ buf.append((char) ch); ++ } ++ } ++ ++ private String readNonNameString() throws IOException ++ { ++ StringBuffer buf = new StringBuffer(); ++ int off = this.off; ++ int len = get(off++); ++ readUTF(buf, off, len); ++ ++ return buf.toString(); ++ } ++ ++ private String readName() throws IOException ++ { ++ StringBuffer buf = new StringBuffer(); ++ int off = this.off; ++ int next = -1; ++ int first = off; ++ ++ while (true) ++ { ++ int len = get(off++); ++ if (len == 0) ++ { ++ break; ++ } ++ switch (len & 0xC0) ++ { ++ case 0x00: ++ //buf.append("[" + off + "]"); ++ readUTF(buf, off, len); ++ off += len; ++ buf.append('.'); ++ break; ++ case 0xC0: ++ //buf.append("<" + (off - 1) + ">"); ++ if (next < 0) ++ { ++ next = off + 1; ++ } ++ off = ((len & 0x3F) << 8) | get(off++); ++ if (off >= first) ++ { ++ throw new IOException("bad domain name: possible circular name detected." + ++ " name start: " + first + ++ " bad offset: 0x" + Integer.toHexString(off)); ++ } ++ first = off; ++ break; ++ default: ++ throw new IOException("unsupported dns label type: '" + Integer.toHexString(len & 0xC0) +"' at " + (off-1)); ++ } ++ } ++ this.off = (next >= 0) ? next : off; ++ return buf.toString(); ++ } ++ ++ /** ++ * Debugging. ++ */ ++ String print(boolean dump) ++ { ++ StringBuffer buf = new StringBuffer(); ++ buf.append(toString() + "\n"); ++ for (Iterator iterator = questions.iterator(); iterator.hasNext();) ++ { ++ buf.append(" ques:" + iterator.next() + "\n"); ++ } ++ int count = 0; ++ for (Iterator iterator = answers.iterator(); iterator.hasNext(); count++) ++ { ++ if (count < numAnswers) ++ { ++ buf.append(" answ:"); ++ } ++ else ++ { ++ if (count < numAnswers + numAuthorities) ++ { ++ buf.append(" auth:"); ++ } ++ else ++ { ++ buf.append(" addi:"); ++ } ++ } ++ buf.append(iterator.next() + "\n"); ++ } ++ if (dump) ++ { ++ for (int off = 0, len = packet.getLength(); off < len; off += 32) ++ { ++ int n = Math.min(32, len - off); ++ if (off < 10) ++ { ++ buf.append(' '); ++ } ++ if (off < 100) ++ { ++ buf.append(' '); ++ } ++ buf.append(off); ++ buf.append(':'); ++ for (int i = 0; i < n; i++) ++ { ++ if ((i % 8) == 0) ++ { ++ buf.append(' '); ++ } ++ buf.append(Integer.toHexString((data[off + i] & 0xF0) >> 4)); ++ buf.append(Integer.toHexString((data[off + i] & 0x0F) >> 0)); ++ } ++ buf.append("\n"); ++ buf.append(" "); ++ for (int i = 0; i < n; i++) ++ { ++ if ((i % 8) == 0) ++ { ++ buf.append(' '); ++ } ++ buf.append(' '); ++ int ch = data[off + i] & 0xFF; ++ buf.append(((ch > ' ') && (ch < 127)) ? (char) ch : '.'); ++ } ++ buf.append("\n"); ++ ++ // limit message size ++ if (off + 32 >= 256) ++ { ++ buf.append("....\n"); ++ break; ++ } ++ } ++ } ++ return buf.toString(); ++ } ++ ++ public String toString() ++ { ++ StringBuffer buf = new StringBuffer(); ++ buf.append(isQuery() ? "dns[query," : "dns[response,"); ++ if (packet.getAddress() != null) ++ { ++ buf.append(packet.getAddress().getHostAddress()); ++ } ++ buf.append(':'); ++ buf.append(packet.getPort()); ++ buf.append(",len="); ++ buf.append(packet.getLength()); ++ buf.append(",id=0x"); ++ buf.append(Integer.toHexString(id)); ++ if (flags != 0) ++ { ++ buf.append(",flags=0x"); ++ buf.append(Integer.toHexString(flags)); ++ if ((flags & DNSConstants.FLAGS_QR_RESPONSE) != 0) ++ { ++ buf.append(":r"); ++ } ++ if ((flags & DNSConstants.FLAGS_AA) != 0) ++ { ++ buf.append(":aa"); ++ } ++ if ((flags & DNSConstants.FLAGS_TC) != 0) ++ { ++ buf.append(":tc"); ++ } ++ } ++ if (numQuestions > 0) ++ { ++ buf.append(",questions="); ++ buf.append(numQuestions); ++ } ++ if (numAnswers > 0) ++ { ++ buf.append(",answers="); ++ buf.append(numAnswers); ++ } ++ if (numAuthorities > 0) ++ { ++ buf.append(",authorities="); ++ buf.append(numAuthorities); ++ } ++ if (numAdditionals > 0) ++ { ++ buf.append(",additionals="); ++ buf.append(numAdditionals); ++ } ++ buf.append("]"); ++ return buf.toString(); ++ } ++ ++ /** ++ * Appends answers to this Incoming. ++ * ++ * @throws IllegalArgumentException If not a query or if Truncated. ++ */ ++ void append(DNSIncoming that) ++ { ++ if (this.isQuery() && this.isTruncated() && that.isQuery()) ++ { ++ if (that.numQuestions > 0) { ++ if (Collections.EMPTY_LIST.equals(this.questions)) ++ this.questions = Collections.synchronizedList(new ArrayList(that.numQuestions)); ++ ++ this.questions.addAll(that.questions); ++ this.numQuestions += that.numQuestions; ++ } ++ ++ if (Collections.EMPTY_LIST.equals(answers)) ++ { ++ answers = Collections.synchronizedList(new ArrayList()); ++ } ++ ++ if (that.numAnswers > 0) ++ { ++ this.answers.addAll(this.numAnswers, that.answers.subList(0, that.numAnswers)); ++ this.numAnswers += that.numAnswers; ++ } ++ if (that.numAuthorities > 0) ++ { ++ this.answers.addAll(this.numAnswers + this.numAuthorities, that.answers.subList(that.numAnswers, that.numAnswers + that.numAuthorities)); ++ this.numAuthorities += that.numAuthorities; ++ } ++ if (that.numAdditionals > 0) ++ { ++ this.answers.addAll(that.answers.subList(that.numAnswers + that.numAuthorities, that.numAnswers + that.numAuthorities + that.numAdditionals)); ++ this.numAdditionals += that.numAdditionals; ++ } ++ } ++ else ++ { ++ throw new IllegalArgumentException(); ++ } ++ } ++ ++ public int elapseSinceArrival() ++ { ++ return (int) (System.currentTimeMillis() - receivedTime); ++ } ++ ++ public List getQuestions() ++ { ++ return questions; ++ } ++ ++ public List getAnswers() ++ { ++ return answers; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/DNSListener.java b/external/asmack/build/src/trunk/org/jmdns/impl/DNSListener.java +new file mode 100644 +index 0000000..d2e3fcb +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/DNSListener.java +@@ -0,0 +1,24 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++ ++package org.jmdns.impl; ++ ++// REMIND: Listener should follow Java idiom for listener or have a different ++// name. ++ ++/** ++ * DNSListener. ++ * Listener for record updates. ++ * ++ * @author Werner Randelshofer, Rick Blair ++ * @version 1.0 May 22, 2004 Created. ++ */ ++interface DNSListener ++{ ++ /** ++ * Update a DNS record. ++ */ ++ void updateRecord(JmDNSImpl jmdns, long now, DNSRecord record); ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/DNSOutgoing.java b/external/asmack/build/src/trunk/org/jmdns/impl/DNSOutgoing.java +new file mode 100644 +index 0000000..b4f0c0e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/DNSOutgoing.java +@@ -0,0 +1,394 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++ ++package org.jmdns.impl; ++ ++import java.io.IOException; ++import java.util.Hashtable; ++import java.util.LinkedList; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++/** ++ * An outgoing DNS message. ++ * ++ * @version %I%, %G% ++ * @author Arthur van Hoff, Rick Blair, Werner Randelshofer ++ */ ++public final class DNSOutgoing ++{ ++ /** ++ * This can be used to turn off domain name compression. This was helpful for ++ * tracking problems interacting with other mdns implementations. ++ */ ++ public static boolean USE_DOMAIN_NAME_COMPRESSION = true; ++ ++ private static Logger logger = Logger.getLogger(DNSOutgoing.class.getName()); ++ int id; ++ int flags; ++ private boolean multicast; ++ private int numQuestions; ++ private int numAnswers; ++ private int numAuthorities; ++ private int numAdditionals; ++ private Hashtable names; ++ ++ byte data[]; ++ int off; ++ int len; ++ ++ /** ++ * Create an outgoing multicast query or response. ++ */ ++ public DNSOutgoing(int flags) ++ { ++ this(flags, true); ++ } ++ ++ /** ++ * Create an outgoing query or response. ++ */ ++ public DNSOutgoing(int flags, boolean multicast) ++ { ++ this.flags = flags; ++ this.multicast = multicast; ++ names = new Hashtable(); ++ data = new byte[DNSConstants.MAX_MSG_TYPICAL]; ++ off = 12; ++ } ++ ++ /** ++ * Add a question to the message. ++ */ ++ public void addQuestion(DNSQuestion rec) throws IOException ++ { ++ if (numAnswers > 0 || numAuthorities > 0 || numAdditionals > 0) ++ { ++ throw new IllegalStateException("Questions must be added before answers"); ++ } ++ numQuestions++; ++ writeQuestion(rec); ++ } ++ ++ /** ++ * Add an answer if it is not suppressed. ++ */ ++ void addAnswer(DNSIncoming in, DNSRecord rec) throws IOException ++ { ++ if (numAuthorities > 0 || numAdditionals > 0) ++ { ++ throw new IllegalStateException("Answers must be added before authorities and additionals"); ++ } ++ if (!rec.suppressedBy(in)) ++ { ++ addAnswer(rec, 0); ++ } ++ } ++ ++ /** ++ * Add an additional answer to the record. Omit if there is no room. ++ */ ++ void addAdditionalAnswer(DNSIncoming in, DNSRecord rec) throws IOException ++ { ++ if ((off < DNSConstants.MAX_MSG_TYPICAL - 200) && !rec.suppressedBy(in)) ++ { ++ writeRecord(rec, 0); ++ numAdditionals++; ++ } ++ } ++ ++ /** ++ * Add an answer to the message. ++ */ ++ public void addAnswer(DNSRecord rec, long now) throws IOException ++ { ++ if (numAuthorities > 0 || numAdditionals > 0) ++ { ++ throw new IllegalStateException("Questions must be added before answers"); ++ } ++ if (rec != null) ++ { ++ if ((now == 0) || !rec.isExpired(now)) ++ { ++ writeRecord(rec, now); ++ numAnswers++; ++ } ++ } ++ } ++ ++ private LinkedList authorativeAnswers = new LinkedList(); ++ ++ /** ++ * Add an authorative answer to the message. ++ */ ++ public void addAuthorativeAnswer(DNSRecord rec) throws IOException ++ { ++ if (numAdditionals > 0) ++ { ++ throw new IllegalStateException("Authorative answers must be added before additional answers"); ++ } ++ authorativeAnswers.add(rec); ++ writeRecord(rec, 0); ++ numAuthorities++; ++ ++ // VERIFY: ++ ++ } ++ ++ void writeByte(int value) throws IOException ++ { ++ if (off >= data.length) ++ { ++ throw new IOException("buffer full"); ++ } ++ data[off++] = (byte) value; ++ } ++ ++ void writeBytes(String str, int off, int len) throws IOException ++ { ++ for (int i = 0; i < len; i++) ++ { ++ writeByte(str.charAt(off + i)); ++ } ++ } ++ ++ void writeBytes(byte data[]) throws IOException ++ { ++ if (data != null) ++ { ++ writeBytes(data, 0, data.length); ++ } ++ } ++ ++ void writeBytes(byte data[], int off, int len) throws IOException ++ { ++ for (int i = 0; i < len; i++) ++ { ++ writeByte(data[off + i]); ++ } ++ } ++ ++ void writeShort(int value) throws IOException ++ { ++ writeByte(value >> 8); ++ writeByte(value); ++ } ++ ++ void writeInt(int value) throws IOException ++ { ++ writeShort(value >> 16); ++ writeShort(value); ++ } ++ ++ void writeUTF(String str, int off, int len) throws IOException ++ { ++ // compute utf length ++ int utflen = 0; ++ for (int i = 0; i < len; i++) ++ { ++ int ch = str.charAt(off + i); ++ if ((ch >= 0x0001) && (ch <= 0x007F)) ++ { ++ utflen += 1; ++ } ++ else ++ { ++ if (ch > 0x07FF) ++ { ++ utflen += 3; ++ } ++ else ++ { ++ utflen += 2; ++ } ++ } ++ } ++ // write utf length ++ writeByte(utflen); ++ // write utf data ++ for (int i = 0; i < len; i++) ++ { ++ int ch = str.charAt(off + i); ++ if ((ch >= 0x0001) && (ch <= 0x007F)) ++ { ++ writeByte(ch); ++ } ++ else ++ { ++ if (ch > 0x07FF) ++ { ++ writeByte(0xE0 | ((ch >> 12) & 0x0F)); ++ writeByte(0x80 | ((ch >> 6) & 0x3F)); ++ writeByte(0x80 | ((ch >> 0) & 0x3F)); ++ } ++ else ++ { ++ writeByte(0xC0 | ((ch >> 6) & 0x1F)); ++ writeByte(0x80 | ((ch >> 0) & 0x3F)); ++ } ++ } ++ } ++ } ++ ++ void writeName(String name) throws IOException ++ { ++ writeName(name, true); ++ } ++ ++ void writeName(String name, boolean useCompression) throws IOException ++ { ++ while (true) ++ { ++ int n = name.indexOf('.'); ++ if (n < 0) ++ { ++ n = name.length(); ++ } ++ if (n <= 0) ++ { ++ writeByte(0); ++ return; ++ } ++ if(useCompression && USE_DOMAIN_NAME_COMPRESSION){ ++ Integer offset = (Integer) names.get(name); ++ if (offset != null) ++ { ++ int val = offset.intValue(); ++ ++ if (val > off) ++ { ++ logger.log(Level.WARNING, "DNSOutgoing writeName failed val=" + val + " name=" + name); ++ } ++ ++ writeByte((val >> 8) | 0xC0); ++ writeByte(val & 0xFF); ++ return; ++ } ++ names.put(name, new Integer(off)); ++ } ++ writeUTF(name, 0, n); ++ name = name.substring(n); ++ if (name.startsWith(".")) ++ { ++ name = name.substring(1); ++ } ++ } ++ } ++ ++ void writeQuestion(DNSQuestion question) throws IOException ++ { ++ writeName(question.name); ++ writeShort(question.type); ++ writeShort(question.clazz); ++ } ++ ++ void writeRecord(DNSRecord rec, long now) throws IOException ++ { ++ int save = off; ++ try ++ { ++ writeName(rec.name); ++ writeShort(rec.type); ++ writeShort(rec.clazz | ((rec.unique && multicast) ? DNSConstants.CLASS_UNIQUE : 0)); ++ writeInt((now == 0) ? rec.getTtl() : rec.getRemainingTTL(now)); ++ writeShort(0); ++ int start = off; ++ rec.write(this); ++ int len = off - start; ++ data[start - 2] = (byte) (len >> 8); ++ data[start - 1] = (byte) (len & 0xFF); ++ } ++ catch (IOException e) ++ { ++ off = save; ++ throw e; ++ } ++ } ++ ++ /** ++ * Finish the message before sending it off. ++ */ ++ void finish() throws IOException ++ { ++ int save = off; ++ off = 0; ++ ++ writeShort(multicast ? 0 : id); ++ writeShort(flags); ++ writeShort(numQuestions); ++ writeShort(numAnswers); ++ writeShort(numAuthorities); ++ writeShort(numAdditionals); ++ off = save; ++ } ++ ++ boolean isQuery() ++ { ++ return (flags & DNSConstants.FLAGS_QR_MASK) == DNSConstants.FLAGS_QR_QUERY; ++ } ++ ++ public boolean isEmpty() ++ { ++ return numQuestions == 0 && numAuthorities == 0 ++ && numAdditionals == 0 && numAnswers == 0; ++ } ++ ++ ++ public String toString() ++ { ++ StringBuffer buf = new StringBuffer(); ++ buf.append(isQuery() ? "dns[query," : "dns[response,"); ++ //buf.append(packet.getAddress().getHostAddress()); ++ buf.append(':'); ++ //buf.append(packet.getPort()); ++ //buf.append(",len="); ++ //buf.append(packet.getLength()); ++ buf.append(",id=0x"); ++ buf.append(Integer.toHexString(id)); ++ if (flags != 0) ++ { ++ buf.append(",flags=0x"); ++ buf.append(Integer.toHexString(flags)); ++ if ((flags & DNSConstants.FLAGS_QR_RESPONSE) != 0) ++ { ++ buf.append(":r"); ++ } ++ if ((flags & DNSConstants.FLAGS_AA) != 0) ++ { ++ buf.append(":aa"); ++ } ++ if ((flags & DNSConstants.FLAGS_TC) != 0) ++ { ++ buf.append(":tc"); ++ } ++ } ++ if (numQuestions > 0) ++ { ++ buf.append(",questions="); ++ buf.append(numQuestions); ++ } ++ if (numAnswers > 0) ++ { ++ buf.append(",answers="); ++ buf.append(numAnswers); ++ } ++ if (numAuthorities > 0) ++ { ++ buf.append(",authorities="); ++ buf.append(numAuthorities); ++ } ++ if (numAdditionals > 0) ++ { ++ buf.append(",additionals="); ++ buf.append(numAdditionals); ++ } ++ buf.append(",\nnames=" + names); ++ buf.append(",\nauthorativeAnswers=" + authorativeAnswers); ++ ++ buf.append("]"); ++ return buf.toString(); ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/DNSQuestion.java b/external/asmack/build/src/trunk/org/jmdns/impl/DNSQuestion.java +new file mode 100644 +index 0000000..8ea1115 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/DNSQuestion.java +@@ -0,0 +1,44 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++ ++package org.jmdns.impl; ++ ++import java.util.logging.Logger; ++ ++/** ++ * A DNS question. ++ * ++ * @version %I%, %G% ++ * @author Arthur van Hoff ++ */ ++public final class DNSQuestion extends DNSEntry ++{ ++ private static Logger logger = Logger.getLogger(DNSQuestion.class.getName()); ++ ++ /** ++ * Create a question. ++ */ ++ public DNSQuestion(String name, int type, int clazz) ++ { ++ super(name, type, clazz); ++ } ++ ++ /** ++ * Check if this question is answered by a given DNS record. ++ */ ++ boolean answeredBy(DNSRecord rec) ++ { ++ return (clazz == rec.clazz) && ((type == rec.type) || (type == DNSConstants.TYPE_ANY)) && ++ name.equals(rec.name); ++ } ++ ++ /** ++ * For debugging only. ++ */ ++ public String toString() ++ { ++ return toString("question", null); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/DNSRecord.java b/external/asmack/build/src/trunk/org/jmdns/impl/DNSRecord.java +new file mode 100644 +index 0000000..9b69935 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/DNSRecord.java +@@ -0,0 +1,742 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++ ++package org.jmdns.impl; ++ ++import java.io.ByteArrayOutputStream; ++import java.io.DataOutputStream; ++import java.io.IOException; ++import java.net.Inet4Address; ++import java.net.InetAddress; ++import java.net.UnknownHostException; ++import java.util.Iterator; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++/** ++ * DNS record ++ * ++ * @version %I%, %G% ++ * @author Arthur van Hoff, Rick Blair, Werner Randelshofer, Pierre Frisch ++ */ ++public abstract class DNSRecord extends DNSEntry ++{ ++ private static Logger logger = Logger.getLogger(DNSRecord.class.getName()); ++ private int ttl; ++ private long created; ++ ++ /** ++ * This source is mainly for debugging purposes, should be the address that ++ * sent this record. ++ */ ++ private InetAddress source; ++ ++ /** ++ * Create a DNSRecord with a name, type, clazz, and ttl. ++ */ ++ DNSRecord(String name, int type, int clazz, int ttl) ++ { ++ super(name, type, clazz); ++ this.ttl = ttl; ++ this.created = System.currentTimeMillis(); ++ } ++ ++ /** ++ * True if this record is the same as some other record. ++ */ ++ public boolean equals(Object other) ++ { ++ return (other instanceof DNSRecord) && sameAs((DNSRecord) other); ++ } ++ ++ /** ++ * True if this record is the same as some other record. ++ */ ++ boolean sameAs(DNSRecord other) ++ { ++ return super.equals(other) && sameValue((DNSRecord) other); ++ } ++ ++ /** ++ * True if this record has the same value as some other record. ++ */ ++ abstract boolean sameValue(DNSRecord other); ++ ++ /** ++ * True if this record has the same type as some other record. ++ */ ++ boolean sameType(DNSRecord other) ++ { ++ return type == other.type; ++ } ++ ++ /** ++ * Handles a query represented by this record. ++ * ++ * @return Returns true if a conflict with one of the services registered ++ * with JmDNS or with the hostname occured. ++ */ ++ abstract boolean handleQuery(JmDNSImpl dns, long expirationTime); ++ ++ /** ++ * Handles a responserepresented by this record. ++ * ++ * @return Returns true if a conflict with one of the services registered ++ * with JmDNS or with the hostname occured. ++ */ ++ abstract boolean handleResponse(JmDNSImpl dns); ++ ++ /** ++ * Adds this as an answer to the provided outgoing datagram. ++ */ ++ abstract DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException; ++ ++ /** ++ * True if this record is suppressed by the answers in a message. ++ */ ++ boolean suppressedBy(DNSIncoming msg) ++ { ++ try ++ { ++ for (int i = msg.numAnswers; i-- > 0;) ++ { ++ if (suppressedBy((DNSRecord) msg.answers.get(i))) ++ { ++ return true; ++ } ++ } ++ return false; ++ } ++ catch (ArrayIndexOutOfBoundsException e) ++ { ++ logger.log(Level.WARNING, "suppressedBy() message " + msg + " exception ", e); ++ // msg.print(true); ++ return false; ++ } ++ } ++ ++ /** ++ * True if this record would be supressed by an answer. ++ * This is the case if this record would not have a ++ * significantly longer TTL. ++ */ ++ boolean suppressedBy(DNSRecord other) ++ { ++ if (sameAs(other) && (other.ttl > ttl / 2)) ++ { ++ return true; ++ } ++ return false; ++ } ++ ++ /** ++ * Get the expiration time of this record. ++ */ ++ long getExpirationTime(int percent) ++ { ++ return created + (percent * ttl * 10L); ++ } ++ ++ /** ++ * Get the remaining TTL for this record. ++ */ ++ int getRemainingTTL(long now) ++ { ++ return (int) Math.max(0, (getExpirationTime(100) - now) / 1000); ++ } ++ ++ /** ++ * Check if the record is expired. ++ */ ++ public boolean isExpired(long now) ++ { ++ return getExpirationTime(100) <= now; ++ } ++ ++ /** ++ * Check if the record is stale, ie it has outlived ++ * more than half of its TTL. ++ */ ++ boolean isStale(long now) ++ { ++ return getExpirationTime(50) <= now; ++ } ++ ++ /** ++ * Reset the TTL of a record. This avoids having to ++ * update the entire record in the cache. ++ */ ++ void resetTTL(DNSRecord other) ++ { ++ created = other.created; ++ ttl = other.ttl; ++ } ++ ++ /** ++ * Write this record into an outgoing message. ++ */ ++ abstract void write(DNSOutgoing out) throws IOException; ++ ++ /** ++ * Address record. ++ */ ++ static class Address extends DNSRecord ++ { ++ private static Logger logger = Logger.getLogger(Address.class.getName()); ++ InetAddress addr; ++ ++ Address(String name, int type, int clazz, int ttl, InetAddress addr) ++ { ++ super(name, type, clazz, ttl); ++ this.addr = addr; ++ } ++ ++ Address(String name, int type, int clazz, int ttl, byte[] rawAddress) ++ { ++ super(name, type, clazz, ttl); ++ try ++ { ++ this.addr = InetAddress.getByAddress(rawAddress); ++ } ++ catch (UnknownHostException exception) ++ { ++ logger.log(Level.WARNING, "Address() exception ", exception); ++ } ++ } ++ ++ void write(DNSOutgoing out) throws IOException ++ { ++ if (addr != null) ++ { ++ byte[] buffer = addr.getAddress(); ++ if (DNSConstants.TYPE_A == type) ++ { ++ // If we have a type A records we should answer with a IPv4 address ++ if (addr instanceof Inet4Address) ++ { ++ // All is good ++ } ++ else ++ { ++ // Get the last four bytes ++ byte[] tempbuffer = buffer; ++ buffer = new byte[4]; ++ System.arraycopy(tempbuffer, 12, buffer, 0, 4); ++ } ++ } ++ else ++ { ++ // If we have a type AAAA records we should answer with a IPv6 address ++ if (addr instanceof Inet4Address) ++ { ++ byte[] tempbuffer = buffer; ++ buffer = new byte[16]; ++ for (int i = 0; i < 16; i++) ++ { ++ if (i < 11) ++ { ++ buffer[i] = tempbuffer[i - 12]; ++ } ++ else ++ { ++ buffer[i] = 0; ++ } ++ } ++ } ++ } ++ int length = buffer.length; ++ out.writeBytes(buffer, 0, length); ++ } ++ } ++ ++ boolean same(DNSRecord other) ++ { ++ return ((sameName(other)) && ((sameValue(other)))); ++ } ++ ++ boolean sameName(DNSRecord other) ++ { ++ return name.equalsIgnoreCase(((Address) other).name); ++ } ++ ++ boolean sameValue(DNSRecord other) ++ { ++ return addr.equals(((Address) other).getAddress()); ++ } ++ ++ InetAddress getAddress() ++ { ++ return addr; ++ } ++ ++ /** ++ * Creates a byte array representation of this record. ++ * This is needed for tie-break tests according to ++ * draft-cheshire-dnsext-multicastdns-04.txt chapter 9.2. ++ */ ++ private byte[] toByteArray() ++ { ++ try ++ { ++ ByteArrayOutputStream bout = new ByteArrayOutputStream(); ++ DataOutputStream dout = new DataOutputStream(bout); ++ dout.write(name.getBytes("UTF8")); ++ dout.writeShort(type); ++ dout.writeShort(clazz); ++ //dout.writeInt(len); ++ byte[] buffer = addr.getAddress(); ++ for (int i = 0; i < buffer.length; i++) ++ { ++ dout.writeByte(buffer[i]); ++ } ++ dout.close(); ++ return bout.toByteArray(); ++ } ++ catch (IOException e) ++ { ++ throw new InternalError(); ++ } ++ } ++ ++ /** ++ * Does a lexicographic comparison of the byte array representation ++ * of this record and that record. ++ * This is needed for tie-break tests according to ++ * draft-cheshire-dnsext-multicastdns-04.txt chapter 9.2. ++ */ ++ private int lexCompare(DNSRecord.Address that) ++ { ++ byte[] thisBytes = this.toByteArray(); ++ byte[] thatBytes = that.toByteArray(); ++ for (int i = 0, n = Math.min(thisBytes.length, thatBytes.length); i < n; i++) ++ { ++ if (thisBytes[i] > thatBytes[i]) ++ { ++ return 1; ++ } ++ else ++ { ++ if (thisBytes[i] < thatBytes[i]) ++ { ++ return -1; ++ } ++ } ++ } ++ return thisBytes.length - thatBytes.length; ++ } ++ ++ /** ++ * Does the necessary actions, when this as a query. ++ */ ++ boolean handleQuery(JmDNSImpl dns, long expirationTime) ++ { ++ DNSRecord.Address dnsAddress = dns.getLocalHost().getDNSAddressRecord(this); ++ if (dnsAddress != null) ++ { ++ if (dnsAddress.sameType(this) && dnsAddress.sameName(this) && (!dnsAddress.sameValue(this))) ++ { ++ logger.finer("handleQuery() Conflicting probe detected. dns state " + dns.getState() + " lex compare " + lexCompare(dnsAddress)); ++ // Tie-breaker test ++ if (dns.getState().isProbing() && lexCompare(dnsAddress) >= 0) ++ { ++ // We lost the tie-break. We have to choose a different name. ++ dns.getLocalHost().incrementHostName(); ++ dns.getCache().clear(); ++ for (Iterator i = dns.getServices().values().iterator(); i.hasNext();) ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) i.next(); ++ info.revertState(); ++ } ++ } ++ dns.revertState(); ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ /** ++ * Does the necessary actions, when this as a response. ++ */ ++ boolean handleResponse(JmDNSImpl dns) ++ { ++ DNSRecord.Address dnsAddress = dns.getLocalHost().getDNSAddressRecord(this); ++ if (dnsAddress != null) ++ { ++ if (dnsAddress.sameType(this) && dnsAddress.sameName(this) && (!dnsAddress.sameValue(this))) ++ { ++ logger.finer("handleResponse() Denial detected"); ++ ++ if (dns.getState().isProbing()) ++ { ++ dns.getLocalHost().incrementHostName(); ++ dns.getCache().clear(); ++ for (Iterator i = dns.getServices().values().iterator(); i.hasNext();) ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) i.next(); ++ info.revertState(); ++ } ++ } ++ dns.revertState(); ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException ++ { ++ return out; ++ } ++ ++ public String toString() ++ { ++ return toString(" address '" + (addr != null ? addr.getHostAddress() : "null") + "'"); ++ } ++ ++ } ++ ++ /** ++ * Pointer record. ++ */ ++ public static class Pointer extends DNSRecord ++ { ++ private static Logger logger = Logger.getLogger(Pointer.class.getName()); ++ String alias; ++ ++ public Pointer(String name, int type, int clazz, int ttl, String alias) ++ { ++ super(name, type, clazz, ttl); ++ this.alias = alias; ++ } ++ ++ void write(DNSOutgoing out) throws IOException ++ { ++ out.writeName(alias); ++ } ++ ++ boolean sameValue(DNSRecord other) ++ { ++ return alias.equals(((Pointer) other).alias); ++ } ++ ++ boolean handleQuery(JmDNSImpl dns, long expirationTime) ++ { ++ // Nothing to do (?) ++ // I think there is no possibility for conflicts for this record type? ++ return false; ++ } ++ ++ boolean handleResponse(JmDNSImpl dns) ++ { ++ // Nothing to do (?) ++ // I think there is no possibility for conflicts for this record type? ++ return false; ++ } ++ ++ String getAlias() ++ { ++ return alias; ++ } ++ ++ DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException ++ { ++ return out; ++ } ++ ++ public String toString() ++ { ++ return toString(alias); ++ } ++ } ++ ++ public static class Text extends DNSRecord ++ { ++ private static Logger logger = Logger.getLogger(Text.class.getName()); ++ byte text[]; ++ ++ public Text(String name, int type, int clazz, int ttl, byte text[]) ++ { ++ super(name, type, clazz, ttl); ++ this.text = text; ++ } ++ ++ void write(DNSOutgoing out) throws IOException ++ { ++ out.writeBytes(text, 0, text.length); ++ } ++ ++ boolean sameValue(DNSRecord other) ++ { ++ Text txt = (Text) other; ++ if (txt.text.length != text.length) ++ { ++ return false; ++ } ++ for (int i = text.length; i-- > 0;) ++ { ++ if (txt.text[i] != text[i]) ++ { ++ return false; ++ } ++ } ++ return true; ++ } ++ ++ boolean handleQuery(JmDNSImpl dns, long expirationTime) ++ { ++ // Nothing to do (?) ++ // I think there is no possibility for conflicts for this record type? ++ return false; ++ } ++ ++ boolean handleResponse(JmDNSImpl dns) ++ { ++ // Nothing to do (?) ++ // Shouldn't we care if we get a conflict at this level? ++ /* ++ ServiceInfo info = (ServiceInfo) dns.services.get(name.toLowerCase()); ++ if (info != null) { ++ if (! Arrays.equals(text,info.text)) { ++ info.revertState(); ++ return true; ++ } ++ }*/ ++ return false; ++ } ++ ++ DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException ++ { ++ return out; ++ } ++ ++ public String toString() ++ { ++ return toString((text.length > 10) ? new String(text, 0, 7) + "..." : new String(text)); ++ } ++ } ++ ++ /** ++ * Service record. ++ */ ++ public static class Service extends DNSRecord ++ { ++ private static Logger logger = Logger.getLogger(Service.class.getName()); ++ int priority; ++ int weight; ++ int port; ++ String server; ++ ++ public Service(String name, int type, int clazz, int ttl, int priority, int weight, int port, String server) ++ { ++ super(name, type, clazz, ttl); ++ this.priority = priority; ++ this.weight = weight; ++ this.port = port; ++ this.server = server; ++ } ++ ++ void write(DNSOutgoing out) throws IOException ++ { ++ out.writeShort(priority); ++ out.writeShort(weight); ++ out.writeShort(port); ++ if(DNSIncoming.USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET){ ++ out.writeName(server, false); ++ } else { ++ out.writeUTF(server, 0, server.length()); ++ ++ // add a zero byte to the end just to be safe, this is the strange form ++ // used by the BonjourConformanceTest ++ out.writeByte(0); ++ } ++ } ++ ++ private byte[] toByteArray() ++ { ++ try ++ { ++ ByteArrayOutputStream bout = new ByteArrayOutputStream(); ++ DataOutputStream dout = new DataOutputStream(bout); ++ dout.write(name.getBytes("UTF8")); ++ dout.writeShort(type); ++ dout.writeShort(clazz); ++ //dout.writeInt(len); ++ dout.writeShort(priority); ++ dout.writeShort(weight); ++ dout.writeShort(port); ++ dout.write(server.getBytes("UTF8")); ++ dout.close(); ++ return bout.toByteArray(); ++ } ++ catch (IOException e) ++ { ++ throw new InternalError(); ++ } ++ } ++ ++ private int lexCompare(DNSRecord.Service that) ++ { ++ byte[] thisBytes = this.toByteArray(); ++ byte[] thatBytes = that.toByteArray(); ++ for (int i = 0, n = Math.min(thisBytes.length, thatBytes.length); i < n; i++) ++ { ++ if (thisBytes[i] > thatBytes[i]) ++ { ++ return 1; ++ } ++ else ++ { ++ if (thisBytes[i] < thatBytes[i]) ++ { ++ return -1; ++ } ++ } ++ } ++ return thisBytes.length - thatBytes.length; ++ } ++ ++ boolean sameValue(DNSRecord other) ++ { ++ Service s = (Service) other; ++ return (priority == s.priority) && (weight == s.weight) && (port == s.port) && server.equals(s.server); ++ } ++ ++ boolean handleQuery(JmDNSImpl dns, long expirationTime) ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(name.toLowerCase()); ++ if (info != null ++ && (port != info.port || !server.equalsIgnoreCase(dns.getLocalHost().getName()))) ++ { ++ logger.finer("handleQuery() Conflicting probe detected from: " + getRecordSource()); ++ DNSRecord.Service localService = new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, ++ DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, ++ DNSConstants.DNS_TTL, info.priority, ++ info.weight, info.port, dns.getLocalHost().getName()); ++ ++ // This block is useful for debugging race conditions when jmdns is respoding to ++ // itself. ++ try ++ { ++ if(dns.getInterface().equals(getRecordSource())){ ++ logger.warning("Got conflicting probe from ourselves\n" + ++ "incoming: " +this.toString() + "\n" + ++ "local : " + localService.toString()); ++ } ++ } ++ catch (IOException e) ++ { ++ e.printStackTrace(); ++ } ++ ++ int comparison = lexCompare(localService); ++ ++ if(comparison == 0){ ++ // the 2 records are identical this probably means we are seeing our own record. ++ // With mutliple interfaces on a single computer it is possible to see our ++ // own records come in on different interfaces than the ones they were sent on. ++ // see section "10. Conflict Resolution" of mdns draft spec. ++ logger.finer("handleQuery() Ignoring a identical service query"); ++ return false; ++ } ++ ++ // Tie breaker test ++ if (info.getState().isProbing() && comparison > 0) ++ { ++ // We lost the tie break ++ String oldName = info.getQualifiedName().toLowerCase(); ++ info.setName(dns.incrementName(info.getName())); ++ dns.getServices().remove(oldName); ++ dns.getServices().put(info.getQualifiedName().toLowerCase(), info); ++ logger.finer("handleQuery() Lost tie break: new unique name chosen:" + info.getName()); ++ ++ // We revert the state to start probing again with the new name ++ info.revertState(); ++ } ++ else ++ { ++ // We won the tie break, so this conflicting probe should be ignored ++ // See paragraph 3 of section 9.2 in mdns draft spec ++ return false; ++ } ++ ++ return true; ++ ++ } ++ return false; ++ } ++ ++ boolean handleResponse(JmDNSImpl dns) ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(name.toLowerCase()); ++ if (info != null ++ && (port != info.port || !server.equalsIgnoreCase(dns.getLocalHost().getName()))) ++ { ++ logger.finer("handleResponse() Denial detected"); ++ ++ if (info.getState().isProbing()) ++ { ++ String oldName = info.getQualifiedName().toLowerCase(); ++ info.setName(dns.incrementName(info.getName())); ++ dns.getServices().remove(oldName); ++ dns.getServices().put(info.getQualifiedName().toLowerCase(), info); ++ logger.finer("handleResponse() New unique name chose:" + info.getName()); ++ ++ } ++ info.revertState(); ++ return true; ++ } ++ return false; ++ } ++ ++ DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(name.toLowerCase()); ++ if (info != null) ++ { ++ if (this.port == info.port != server.equals(dns.getLocalHost().getName())) ++ { ++ return dns.addAnswer(in, addr, port, out, ++ new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, ++ DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, ++ DNSConstants.DNS_TTL, info.priority, ++ info.weight, info.port, dns.getLocalHost().getName())); ++ } ++ } ++ return out; ++ } ++ ++ public String toString() ++ { ++ return toString(server + ":" + port); ++ } ++ } ++ ++ public void setRecordSource(InetAddress source) ++ { ++ this.source = source; ++ } ++ ++ public InetAddress getRecordSource() ++ { ++ return source; ++ } ++ ++ public String toString(String other) ++ { ++ return toString("record", ttl + "/" + getRemainingTTL(System.currentTimeMillis()) + "," + other); ++ } ++ ++ public void setTtl(int ttl) ++ { ++ this.ttl = ttl; ++ } ++ ++ public int getTtl() ++ { ++ return ttl; ++ } ++} ++ +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/DNSState.java b/external/asmack/build/src/trunk/org/jmdns/impl/DNSState.java +new file mode 100644 +index 0000000..cb79088 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/DNSState.java +@@ -0,0 +1,111 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package org.jmdns.impl; ++ ++import java.util.ArrayList; ++import java.util.logging.Logger; ++ ++/** ++ * DNSState defines the possible states for services registered with JmDNS. ++ * ++ * @author Werner Randelshofer, Rick Blair ++ * @version 1.0 May 23, 2004 Created. ++ */ ++public class DNSState implements Comparable ++{ ++ private static Logger logger = Logger.getLogger(DNSState.class.getName()); ++ ++ private final String name; ++ ++ /** ++ * Ordinal of next state to be created. ++ */ ++ private static int nextOrdinal = 0; ++ /** ++ * Assign an ordinal to this state. ++ */ ++ private final int ordinal = nextOrdinal++; ++ /** ++ * Logical sequence of states. ++ * The sequence is consistent with the ordinal of a state. ++ * This is used for advancing through states. ++ */ ++ private final static ArrayList sequence = new ArrayList(); ++ ++ private DNSState(String name) ++ { ++ this.name = name; ++ sequence.add(this); ++ } ++ ++ public final String toString() ++ { ++ return name; ++ } ++ ++ public static final DNSState PROBING_1 = new DNSState("probing 1"); ++ public static final DNSState PROBING_2 = new DNSState("probing 2"); ++ public static final DNSState PROBING_3 = new DNSState("probing 3"); ++ public static final DNSState ANNOUNCING_1 = new DNSState("announcing 1"); ++ public static final DNSState ANNOUNCING_2 = new DNSState("announcing 2"); ++ public static final DNSState ANNOUNCED = new DNSState("announced"); ++ public static final DNSState CANCELED = new DNSState("canceled"); ++ ++ /** ++ * Returns the next advanced state. ++ * In general, this advances one step in the following sequence: PROBING_1, ++ * PROBING_2, PROBING_3, ANNOUNCING_1, ANNOUNCING_2, ANNOUNCED. ++ * Does not advance for ANNOUNCED and CANCELED state. ++ */ ++ public final DNSState advance() ++ { ++ return (isProbing() || isAnnouncing()) ? (DNSState) sequence.get(ordinal + 1) : this; ++ } ++ ++ /** ++ * Returns to the next reverted state. ++ * All states except CANCELED revert to PROBING_1. ++ * Status CANCELED does not revert. ++ */ ++ public final DNSState revert() ++ { ++ return (this == CANCELED) ? this : PROBING_1; ++ } ++ ++ /** ++ * Returns true, if this is a probing state. ++ */ ++ public boolean isProbing() ++ { ++ return compareTo(PROBING_1) >= 0 && compareTo(PROBING_3) <= 0; ++ } ++ ++ /** ++ * Returns true, if this is an announcing state. ++ */ ++ public boolean isAnnouncing() ++ { ++ return compareTo(ANNOUNCING_1) >= 0 && compareTo(ANNOUNCING_2) <= 0; ++ } ++ ++ /** ++ * Returns true, if this is an announced state. ++ */ ++ public boolean isAnnounced() ++ { ++ return compareTo(ANNOUNCED) == 0; ++ } ++ ++ /** ++ * Compares two states. ++ * The states compare as follows: ++ * PROBING_1 < PROBING_2 < PROBING_3 < ANNOUNCING_1 < ++ * ANNOUNCING_2 < RESPONDING < ANNOUNCED < CANCELED. ++ */ ++ public int compareTo(Object o) ++ { ++ return ordinal - ((DNSState) o).ordinal; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/HostInfo.java b/external/asmack/build/src/trunk/org/jmdns/impl/HostInfo.java +new file mode 100755 +index 0000000..a3a08f9 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/HostInfo.java +@@ -0,0 +1,172 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++ ++ ++package org.jmdns.impl; ++ ++import java.io.IOException; ++import java.net.DatagramPacket; ++import java.net.Inet4Address; ++import java.net.Inet6Address; ++import java.net.InetAddress; ++import java.net.NetworkInterface; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++/** ++ * HostInfo information on the local host to be able to cope with change of addresses. ++ * ++ * @version %I%, %G% ++ * @author Pierre Frisch, Werner Randelshofer ++ */ ++public class HostInfo ++{ ++ private static Logger logger = Logger.getLogger(HostInfo.class.getName()); ++ protected String name; ++ protected InetAddress address; ++ protected NetworkInterface interfaze; ++ /** ++ * This is used to create a unique name for the host name. ++ */ ++ private int hostNameCount; ++ ++ public HostInfo(InetAddress address, String name) ++ { ++ super(); ++ this.address = address; ++ this.name = name; ++ if (address != null) ++ { ++ try ++ { ++ interfaze = NetworkInterface.getByInetAddress(address); ++ } ++ catch (Exception exception) ++ { ++ // FIXME Shouldn't we take an action here? ++ logger.log(Level.WARNING, "LocalHostInfo() exception ", exception); ++ } ++ } ++ } ++ ++ public String getName() ++ { ++ return name; ++ } ++ ++ public InetAddress getAddress() ++ { ++ return address; ++ } ++ ++ public NetworkInterface getInterface() ++ { ++ return interfaze; ++ } ++ ++ synchronized String incrementHostName() ++ { ++ hostNameCount++; ++ int plocal = name.indexOf(".local."); ++ int punder = name.lastIndexOf("-"); ++ name = name.substring(0, (punder == -1 ? plocal : punder)) + "-" + hostNameCount + ".local."; ++ return name; ++ } ++ ++ boolean shouldIgnorePacket(DatagramPacket packet) ++ { ++ boolean result = false; ++ if (getAddress() != null) ++ { ++ InetAddress from = packet.getAddress(); ++ if (from != null) ++ { ++ if (from.isLinkLocalAddress() && (!getAddress().isLinkLocalAddress())) ++ { ++ // Ignore linklocal packets on regular interfaces, unless this is ++ // also a linklocal interface. This is to avoid duplicates. This is ++ // a terrible hack caused by the lack of an API to get the address ++ // of the interface on which the packet was received. ++ result = true; ++ } ++ if (from.isLoopbackAddress() && (!getAddress().isLoopbackAddress())) ++ { ++ // Ignore loopback packets on a regular interface unless this is ++ // also a loopback interface. ++ result = true; ++ } ++ } ++ } ++ return result; ++ } ++ ++ DNSRecord.Address getDNSAddressRecord(DNSRecord.Address address) ++ { ++ return (DNSConstants.TYPE_AAAA == address.type ? getDNS6AddressRecord() : getDNS4AddressRecord()); ++ } ++ ++ public DNSRecord.Address getDNS4AddressRecord() ++ { ++ if ((getAddress() != null) && ++ ((getAddress() instanceof Inet4Address) || ++ ((getAddress() instanceof Inet6Address) && (((Inet6Address) getAddress()).isIPv4CompatibleAddress())))) ++ { ++ return new DNSRecord.Address(getName(), DNSConstants.TYPE_A, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, getAddress()); ++ } ++ return null; ++ } ++ ++ public DNSRecord.Address getDNS6AddressRecord() ++ { ++ if ((getAddress() != null) && (getAddress() instanceof Inet6Address)) ++ { ++ return new DNSRecord.Address(getName(), DNSConstants.TYPE_AAAA, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, getAddress()); ++ } ++ return null; ++ } ++ ++ public String toString() ++ { ++ StringBuffer buf = new StringBuffer(); ++ buf.append("local host info["); ++ buf.append(getName() != null ? getName() : "no name"); ++ buf.append(", "); ++ buf.append(getInterface() != null ? getInterface().getDisplayName() : "???"); ++ buf.append(":"); ++ buf.append(getAddress() != null ? getAddress().getHostAddress() : "no address"); ++ buf.append("]"); ++ return buf.toString(); ++ } ++ ++ public void addAddressRecords(DNSOutgoing out, boolean authoritative) throws IOException ++ { ++ DNSRecord answer = getDNS4AddressRecord(); ++ if (answer != null) ++ { ++ if (authoritative) ++ { ++ out.addAuthorativeAnswer(answer); ++ } ++ else ++ { ++ out.addAnswer(answer, 0); ++ } ++ } ++ ++ answer = getDNS6AddressRecord(); ++ if (answer != null) ++ { ++ if (authoritative) ++ { ++ out.addAuthorativeAnswer(answer); ++ } ++ else ++ { ++ out.addAnswer(answer, 0); ++ } ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/JmDNSImpl.java b/external/asmack/build/src/trunk/org/jmdns/impl/JmDNSImpl.java +new file mode 100644 +index 0000000..6ef236a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/JmDNSImpl.java +@@ -0,0 +1,1661 @@ ++///Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package org.jmdns.impl; ++ ++import java.io.IOException; ++import java.net.DatagramPacket; ++import java.net.InetAddress; ++import java.net.MulticastSocket; ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.Hashtable; ++import java.util.Iterator; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Map; ++import java.util.Random; ++import java.util.Timer; ++import java.util.TimerTask; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++import org.jmdns.JmDNS; ++import org.jmdns.ServiceEvent; ++import org.jmdns.ServiceInfo; ++import org.jmdns.ServiceListener; ++import org.jmdns.ServiceTypeListener; ++import org.jmdns.impl.tasks.Announcer; ++import org.jmdns.impl.tasks.TextAnnouncer; ++import org.jmdns.impl.tasks.Canceler; ++import org.jmdns.impl.tasks.Prober; ++import org.jmdns.impl.tasks.RecordReaper; ++import org.jmdns.impl.tasks.Renewer; ++import org.jmdns.impl.tasks.Responder; ++import org.jmdns.impl.tasks.ServiceInfoResolver; ++import org.jmdns.impl.tasks.ServiceResolver; ++import org.jmdns.impl.tasks.TypeResolver; ++ ++// REMIND: multiple IP addresses ++ ++/** ++ * mDNS implementation in Java. ++ * ++ * @version %I%, %G% ++ * @author Arthur van Hoff, Rick Blair, Jeff Sonstein, Werner Randelshofer, ++ * Pierre Frisch, Scott Lewis ++ */ ++public class JmDNSImpl extends JmDNS ++{ ++ private static Logger logger = Logger.getLogger(JmDNSImpl.class.getName()); ++ ++ /** ++ * This is the multicast group, we are listening to for multicast DNS ++ * messages. ++ */ ++ private InetAddress group; ++ /** ++ * This is our multicast socket. ++ */ ++ private MulticastSocket socket; ++ ++ /** ++ * Used to fix live lock problem on unregester. ++ */ ++ ++ private boolean closed = false; ++ ++ /** ++ * Holds instances of JmDNS.DNSListener. Must by a synchronized collection, ++ * because it is updated from concurrent threads. ++ */ ++ private List listeners; ++ /** ++ * Holds instances of ServiceListener's. Keys are Strings holding a fully ++ * qualified service type. Values are LinkedList's of ServiceListener's. ++ */ ++ private Map serviceListeners; ++ /** ++ * Holds instances of ServiceTypeListener's. ++ */ ++ private List typeListeners; ++ ++ /** ++ * Cache for DNSEntry's. ++ */ ++ private DNSCache cache; ++ ++ /** ++ * This hashtable holds the services that have been registered. Keys are ++ * instances of String which hold an all lower-case version of the fully ++ * qualified service name. Values are instances of ServiceInfo. ++ */ ++ Map services; ++ ++ /** ++ * This hashtable holds the service types that have been registered or that ++ * have been received in an incoming datagram. Keys are instances of String ++ * which hold an all lower-case version of the fully qualified service type. ++ * Values hold the fully qualified service type. ++ */ ++ Map serviceTypes; ++ /** ++ * This is the shutdown hook, we registered with the java runtime. ++ */ ++ private Thread shutdown; ++ ++ /** ++ * Handle on the local host ++ */ ++ private HostInfo localHost; ++ ++ private Thread incomingListener = null; ++ ++ /** ++ * Throttle count. This is used to count the overall number of probes sent ++ * by JmDNS. When the last throttle increment happened . ++ */ ++ private int throttle; ++ /** ++ * Last throttle increment. ++ */ ++ private long lastThrottleIncrement; ++ ++ /** ++ * The timer is used to dispatch all outgoing messages of JmDNS. It is also ++ * used to dispatch maintenance tasks for the DNS cache. ++ */ ++ Timer timer; ++ ++ /** ++ * The source for random values. This is used to introduce random delays in ++ * responses. This reduces the potential for collisions on the network. ++ */ ++ private final static Random random = new Random(); ++ ++ /** ++ * This lock is used to coordinate processing of incoming and outgoing ++ * messages. This is needed, because the Rendezvous Conformance Test does ++ * not forgive race conditions. ++ */ ++ private Object ioLock = new Object(); ++ ++ /** ++ * If an incoming package which needs an answer is truncated, we store it ++ * here. We add more incoming DNSRecords to it, until the JmDNS.Responder ++ * timer picks it up. Remind: This does not work well with multiple planned ++ * answers for packages that came in from different clients. ++ */ ++ private DNSIncoming plannedAnswer; ++ ++ // State machine ++ /** ++ * The state of JmDNS.

      For proper handling of concurrency, this ++ * variable must be changed only using methods advanceState(), revertState() ++ * and cancel(). ++ */ ++ private DNSState state = DNSState.PROBING_1; ++ ++ /** ++ * Timer task associated to the host name. This is used to prevent from ++ * having multiple tasks associated to the host name at the same time. ++ */ ++ private TimerTask task; ++ ++ /** ++ * This hashtable is used to maintain a list of service types being ++ * collected by this JmDNS instance. The key of the hashtable is a service ++ * type name, the value is an instance of JmDNS.ServiceCollector. ++ * ++ * @see #list ++ */ ++ private final HashMap serviceCollectors = new HashMap(); ++ ++ /** ++ * Create an instance of JmDNS. ++ */ ++ public JmDNSImpl() throws IOException ++ { ++ logger.finer("JmDNS instance created"); ++ /*try ++ {*/ ++ final InetAddress addr = InetAddress.getLocalHost(); ++ init(addr.isLoopbackAddress() ? null : addr, addr.getHostName()); // [ ++ // PJYF ++ // Oct ++ // 14 ++ // 2004 ++ // ] ++ // Why ++ // do ++ // we ++ // disallow ++ // the ++ // loopback ++ // address ++ // ? ++ /*} ++ catch (final IOException e) ++ { ++ init(null, "computer"); ++ }*/ ++ } ++ ++ /** ++ * Create an instance of JmDNS and bind it to a specific network interface ++ * given its IP-address. ++ */ ++ public JmDNSImpl(InetAddress addr) throws IOException ++ { ++ /*try ++ {*/ ++ String name = addr.getHostName(); ++ init(addr, name); ++ /*} ++ catch (final IOException e) ++ { ++ init(null, name); ++ }*/ ++ } ++ ++ /** ++ * Initialize everything. ++ * ++ * @param address ++ * The interface to which JmDNS binds to. ++ * @param name ++ * The host name of the interface. ++ */ ++ private void init(InetAddress address, String name) throws IOException ++ { ++ // A host name with "." is illegal. so strip off everything and append . ++ // local. ++ final int idx = name.indexOf("."); ++ if (idx > 0) ++ { ++ name = name.substring(0, idx); ++ } ++ name += ".local."; ++ // localHost to IP address binding ++ localHost = new HostInfo(address, name); ++ ++ cache = new DNSCache(100); ++ ++ listeners = Collections.synchronizedList(new ArrayList()); ++ serviceListeners = new HashMap(); ++ typeListeners = new ArrayList(); ++ ++ services = new Hashtable(20); ++ serviceTypes = new Hashtable(20); ++ ++ // REMIND: If I could pass in a name for the Timer thread, ++ // I would pass' JmDNS.Timer'. ++ timer = new Timer(); ++ new RecordReaper(this).start(timer); ++ shutdown = new Thread(new Shutdown(), "JmDNS.Shutdown"); ++ Runtime.getRuntime().addShutdownHook(shutdown); ++ ++ incomingListener = new Thread(new SocketListener(this), "JmDNS.SocketListener"); ++ ++ // Bind to multicast socket ++ openMulticastSocket(getLocalHost()); ++ start(getServices().values()); ++ } ++ ++ private void start(Collection serviceInfos) ++ { ++ setState(DNSState.PROBING_1); ++ incomingListener.start(); ++ new Prober(this).start(timer); ++ for (final Iterator iterator = serviceInfos.iterator(); iterator.hasNext();) ++ { ++ try ++ { ++ registerService(new ServiceInfoImpl((ServiceInfoImpl) iterator.next())); ++ } ++ catch (final Exception exception) ++ { ++ logger.log(Level.WARNING, "start() Registration exception ", exception); ++ } ++ } ++ } ++ ++ private void openMulticastSocket(HostInfo hostInfo) throws IOException ++ { ++ if (group == null) ++ { ++ group = InetAddress.getByName(DNSConstants.MDNS_GROUP); ++ } ++ if (socket != null) ++ { ++ this.closeMulticastSocket(); ++ } ++ socket = new MulticastSocket(DNSConstants.MDNS_PORT); ++ if ((hostInfo != null) && (localHost.getInterface() != null)) ++ { ++ socket.setNetworkInterface(hostInfo.getInterface()); ++ } ++ socket.setTimeToLive(255); ++ socket.joinGroup(group); ++ } ++ ++ private void closeMulticastSocket() ++ { ++ logger.finer("closeMulticastSocket()"); ++ if (socket != null) ++ { ++ // close socket ++ try ++ { ++ socket.leaveGroup(group); ++ socket.close(); ++ if (incomingListener != null) ++ { ++ incomingListener.join(); ++ } ++ } ++ catch (final Exception exception) ++ { ++ logger.log(Level.WARNING, "closeMulticastSocket() Close socket exception ", ++ exception); ++ } ++ socket = null; ++ } ++ } ++ ++ // State machine ++ /** ++ * Sets the state and notifies all objects that wait on JmDNS. ++ */ ++ public synchronized void advanceState() ++ { ++ setState(getState().advance()); ++ notifyAll(); ++ } ++ ++ /** ++ * Sets the state and notifies all objects that wait on JmDNS. ++ */ ++ synchronized void revertState() ++ { ++ setState(getState().revert()); ++ notifyAll(); ++ } ++ ++ /** ++ * Sets the state and notifies all objects that wait on JmDNS. ++ */ ++ synchronized void cancel() ++ { ++ setState(DNSState.CANCELED); ++ notifyAll(); ++ } ++ ++ /** ++ * Returns the current state of this info. ++ */ ++ public DNSState getState() ++ { ++ return state; ++ } ++ ++ /** ++ * Return the DNSCache associated with the cache variable ++ */ ++ public DNSCache getCache() ++ { ++ return cache; ++ } ++ ++ /** ++ * @see org.jmdns.JmDNS#getHostName() ++ */ ++ public String getHostName() ++ { ++ return localHost.getName(); ++ } ++ ++ public HostInfo getLocalHost() ++ { ++ return localHost; ++ } ++ ++ /** ++ * @see org.jmdns.JmDNS#getInterface() ++ */ ++ public InetAddress getInterface() throws IOException ++ { ++ return socket.getInterface(); ++ } ++ ++ /** ++ * @see org.jmdns.JmDNS#getServiceInfo(java.lang.String, java.lang.String) ++ */ ++ public ServiceInfo getServiceInfo(String type, String name) ++ { ++ return getServiceInfo(type, name, 3 * 1000); ++ } ++ ++ /** ++ * @see org.jmdns.JmDNS#getServiceInfo(java.lang.String, java.lang.String, ++ * int) ++ */ ++ public ServiceInfo getServiceInfo(String type, String name, int timeout) ++ { ++ final ServiceInfoImpl info = new ServiceInfoImpl(type, name); ++ new ServiceInfoResolver(this, info).start(timer); ++ ++ try ++ { ++ final long end = System.currentTimeMillis() + timeout; ++ long delay; ++ synchronized (info) ++ { ++ while (!info.hasData() && (delay = end - System.currentTimeMillis()) > 0) ++ { ++ info.wait(delay); ++ } ++ } ++ } ++ catch (final InterruptedException e) ++ { ++ // empty ++ } ++ ++ return (info.hasData()) ? info : null; ++ } ++ ++ /** ++ * @see org.jmdns.JmDNS#requestServiceInfo(java.lang.String, ++ * java.lang.String) ++ */ ++ public void requestServiceInfo(String type, String name) ++ { ++ requestServiceInfo(type, name, false, 3 * 1000); ++ } ++ /** ++ * @see org.jmdns.JmDNS#requestServiceInfo(java.lang.String, ++ * java.lang.String, boolean) ++ */ ++ public void requestServiceInfo(String type, String name, boolean persistent) ++ { ++ requestServiceInfo(type, name, persistent, 3 * 1000); ++ } ++ ++ /** ++ * @see org.jmdns.JmDNS#requestServiceInfo(java.lang.String, ++ * java.lang.String, int) ++ */ ++ public void requestServiceInfo(String type, String name, int timeout) ++ { ++ requestServiceInfo(type, name, false, timeout); ++ } ++ ++ /** ++ * @see org.jmdns.JmDNS#requestServiceInfo(java.lang.String, ++ * java.lang.String, boolean, int) ++ */ ++ public void requestServiceInfo(String type, String name, ++ boolean persistent, int timeout) ++ { ++ registerServiceType(type); ++ final ServiceInfoImpl info = new ServiceInfoImpl(type, name); ++ new ServiceInfoResolver(this, info, persistent).start(timer); ++ ++ try ++ { ++ final long end = System.currentTimeMillis() + timeout; ++ long delay; ++ synchronized (info) ++ { ++ while (!info.hasData() && (delay = end - System.currentTimeMillis()) > 0) ++ { ++ info.wait(delay); ++ } ++ } ++ } ++ catch (final InterruptedException e) ++ { ++ // empty ++ } ++ } ++ ++ /** ++ * @see org.jmdns.JmDNS# ++ */ ++ ++ void handleServiceResolved(ServiceInfoImpl info) ++ { ++ List list = null; ++ ArrayList listCopy = null; ++ synchronized (serviceListeners) ++ { ++ list = (List) serviceListeners.get(info.type.toLowerCase()); ++ ++ if (list != null) ++ { ++ listCopy = new ArrayList(list); ++ } ++ } ++ if (listCopy != null) ++ { ++ final ServiceEvent event = new ServiceEventImpl(this, info.type, info.getName(), info); ++ for (final Iterator iterator = listCopy.iterator(); iterator.hasNext();) ++ { ++ ((ServiceListener) iterator.next()).serviceResolved(event); ++ } ++ } ++ } ++ ++ void handleNewTXT(ServiceInfoImpl info) { ++ handleServiceResolved(info); ++ } ++ ++ /** ++ * @see ++ * org.jmdns.JmDNS#addServiceTypeListener(org.jmdns.ServiceTypeListener ++ * ) ++ */ ++ public void addServiceTypeListener(ServiceTypeListener listener) throws IOException ++ { ++ synchronized (this) ++ { ++ typeListeners.remove(listener); ++ typeListeners.add(listener); ++ } ++ ++ // report cached service types ++ for (final Iterator iterator = serviceTypes.values().iterator(); iterator.hasNext();) ++ { ++ listener.serviceTypeAdded(new ServiceEventImpl(this, (String) iterator.next(), null, ++ null)); ++ } ++ ++ new TypeResolver(this).start(timer); ++ } ++ ++ /** ++ * @see org.jmdns.JmDNS#removeServiceTypeListener(org.jmdns. ++ * ServiceTypeListener) ++ */ ++ public void removeServiceTypeListener(ServiceTypeListener listener) ++ { ++ synchronized (this) ++ { ++ typeListeners.remove(listener); ++ } ++ } ++ ++ /** ++ * @see org.jmdns.JmDNS#addServiceListener(java.lang.String, ++ * org.jmdns.ServiceListener) ++ */ ++ public void addServiceListener(String type, ServiceListener listener) ++ { ++ final String lotype = type.toLowerCase(); ++ removeServiceListener(lotype, listener); ++ List list = null; ++ ++ synchronized (serviceListeners) ++ { ++ list = (List) serviceListeners.get(lotype); ++ if (list == null) ++ { ++ list = Collections.synchronizedList(new LinkedList()); ++ serviceListeners.put(lotype, list); ++ } ++ list.add(listener); ++ } ++ ++ // report cached service types ++ final List serviceEvents = new ArrayList(); ++ synchronized (cache) ++ { ++ for (final Iterator i = cache.iterator(); i.hasNext();) ++ { ++ for (DNSCache.CacheNode n = (DNSCache.CacheNode) i.next(); n != null; n = n.next()) ++ { ++ final DNSRecord rec = (DNSRecord) n.getValue(); ++ if (rec.type == DNSConstants.TYPE_SRV) ++ { ++ if (rec.name.endsWith(type)) ++ { ++ serviceEvents.add(new ServiceEventImpl(this, type, toUnqualifiedName( ++ type, rec.name), null)); ++ } ++ } ++ } ++ } ++ } ++ // Actually call listener with all service events added above ++ for (final Iterator i = serviceEvents.iterator(); i.hasNext();) ++ { ++ listener.serviceAdded((ServiceEventImpl) i.next()); ++ } ++ // Create/start ServiceResolver ++ new ServiceResolver(this, type).start(timer); ++ } ++ ++ /** ++ * @see org.jmdns.JmDNS#removeServiceListener(java.lang.String, ++ * org.jmdns.ServiceListener) ++ */ ++ public void removeServiceListener(String type, ServiceListener listener) ++ { ++ type = type.toLowerCase(); ++ List list = null; ++ synchronized (serviceListeners) ++ { ++ list = (List) serviceListeners.get(type); ++ if (list != null) ++ { ++ list.remove(listener); ++ if (list.size() == 0) ++ { ++ serviceListeners.remove(type); ++ } ++ } ++ } ++ } ++ ++ /** ++ * @see org.jmdns.JmDNS#registerService(org.jmdns.ServiceInfo) ++ */ ++ public void registerService(ServiceInfo infoAbstract) throws IOException ++ { ++ final ServiceInfoImpl info = (ServiceInfoImpl) infoAbstract; ++ ++ registerServiceType(info.type); ++ ++ // bind the service to this address ++ info.server = localHost.getName(); ++ info.addr = localHost.getAddress(); ++ ++ synchronized (this) ++ { ++ makeServiceNameUnique(info); ++ services.put(info.getQualifiedName().toLowerCase(), info); ++ } ++ ++ new /* Service */Prober(this).start(timer); ++ try ++ { ++ synchronized (info) ++ { ++ while (info.getState().compareTo(DNSState.ANNOUNCED) < 0) ++ { ++ info.wait(); ++ } ++ } ++ } ++ catch (final InterruptedException e) ++ { ++ // empty ++ } ++ logger.fine("registerService() JmDNS registered service as " + info); ++ } ++ ++ /** ++ * @see org.jmdns.JMDNS#reannounceService(org.jmdns.ServiceInfo) ++ */ ++ public void reannounceService(org.jmdns.ServiceInfo infoAbstract) throws IOException ++ { ++ ++ final ServiceInfoImpl info = (ServiceInfoImpl) infoAbstract; ++ ++ if (services.containsKey(info.getQualifiedName().toLowerCase())) ++ { ++ ServiceInfoImpl info_tmp = (ServiceInfoImpl)services.get(info.getQualifiedName().toLowerCase()); ++ ++ info.setStateAnnounce(); ++ ++ startTextAnnouncer(); ++ //new /* Service */Prober(this).start(timer); ++ try ++ { ++ synchronized (info) ++ { ++ while (info.getState().compareTo(DNSState.ANNOUNCED) < 0) ++ { ++ info.wait(); ++ } ++ } ++ } ++ catch (final InterruptedException e) ++ { ++ // empty ++ } ++ logger.fine("reannounceService() JmDNS reannounced service " + info); ++ } ++ else ++ { ++ logger.fine("reannounceService() service to be announced not found: " + info); ++ } ++ } ++ ++ /** ++ * @see org.jmdns.JmDNS#unregisterService(org.jmdns.ServiceInfo) ++ */ ++ public void unregisterService(ServiceInfo infoAbstract) ++ { ++ final ServiceInfoImpl info = (ServiceInfoImpl) infoAbstract; ++ synchronized (this) ++ { ++ services.remove(info.getQualifiedName().toLowerCase()); ++ } ++ info.cancel(); ++ ++ // Note: We use this lock object to synchronize on it. ++ // Synchronizing on another object (e.g. the ServiceInfo) does ++ // not make sense, because the sole purpose of the lock is to ++ // wait until the canceler has finished. If we synchronized on ++ // the ServiceInfo or on the Canceler, we would block all ++ // accesses to synchronized methods on that object. This is not ++ // what we want! ++ final Object lock = new Object(); ++ new Canceler(this, info, lock).start(timer); ++ ++ // Remind: We get a deadlock here, if the Canceler does not run! ++ try ++ { ++ synchronized (lock) ++ { ++ lock.wait(); ++ } ++ } ++ catch (final InterruptedException e) ++ { ++ // empty ++ } ++ } ++ ++ /** ++ * @see org.jmdns.JmDNS#unregisterAllServices() ++ */ ++ public void unregisterAllServices() ++ { ++ logger.finer("unregisterAllServices()"); ++ if (services.size() == 0) ++ { ++ return; ++ } ++ ++ Collection list; ++ synchronized (this) ++ { ++ list = new LinkedList(services.values()); ++ services.clear(); ++ } ++ for (final Iterator iterator = list.iterator(); iterator.hasNext();) ++ { ++ ((ServiceInfoImpl) iterator.next()).cancel(); ++ } ++ ++ final Object lock = new Object(); ++ new Canceler(this, list, lock).start(timer); ++ // Remind: We get a livelock here, if the Canceler does not run! ++ try ++ { ++ synchronized (lock) ++ { ++ if (!closed) ++ { ++ lock.wait(); ++ } ++ } ++ } ++ catch (final InterruptedException e) ++ { ++ // empty ++ } ++ ++ } ++ ++ /** ++ * @see org.jmdns.JmDNS#registerServiceType(java.lang.String) ++ */ ++ public void registerServiceType(String type) ++ { ++ final String name = type.toLowerCase(); ++ if (serviceTypes.get(name) == null) ++ { ++ if ((type.indexOf("._mdns._udp.") < 0) && !type.endsWith(".in-addr.arpa.")) ++ { ++ Collection list; ++ synchronized (this) ++ { ++ serviceTypes.put(name, type); ++ list = new LinkedList(typeListeners); ++ } ++ for (final Iterator iterator = list.iterator(); iterator.hasNext();) ++ { ++ ((ServiceTypeListener) iterator.next()).serviceTypeAdded(new ServiceEventImpl( ++ this, type, null, null)); ++ } ++ } ++ } ++ } ++ ++ /** ++ * Generate a possibly unique name for a host using the information we have ++ * in the cache. ++ * ++ * @return returns true, if the name of the host had to be changed. ++ */ ++ private boolean makeHostNameUnique(DNSRecord.Address host) ++ { ++ final String originalName = host.getName(); ++ System.currentTimeMillis(); ++ ++ boolean collision; ++ do ++ { ++ collision = false; ++ ++ // Check for collision in cache ++ for (DNSCache.CacheNode j = cache.find(host.getName().toLowerCase()); j != null; j = j ++ .next()) ++ { ++ if (false) ++ { ++ host.name = incrementName(host.getName()); ++ collision = true; ++ break; ++ } ++ } ++ } ++ while (collision); ++ ++ if (originalName.equals(host.getName())) ++ { ++ return false; ++ } ++ else ++ { ++ return true; ++ } ++ } ++ ++ /** ++ * Generate a possibly unique name for a service using the information we ++ * have in the cache. ++ * ++ * @return returns true, if the name of the service info had to be changed. ++ */ ++ private boolean makeServiceNameUnique(ServiceInfoImpl info) ++ { ++ final String originalQualifiedName = info.getQualifiedName(); ++ final long now = System.currentTimeMillis(); ++ ++ boolean collision; ++ do ++ { ++ collision = false; ++ ++ // Check for collision in cache ++ for (DNSCache.CacheNode j = cache.find(info.getQualifiedName().toLowerCase()); j != null; j = j ++ .next()) ++ { ++ final DNSRecord a = (DNSRecord) j.getValue(); ++ if ((a.type == DNSConstants.TYPE_SRV) && !a.isExpired(now)) ++ { ++ final DNSRecord.Service s = (DNSRecord.Service) a; ++ if (s.port != info.port || !s.server.equals(localHost.getName())) ++ { ++ logger ++ .finer("makeServiceNameUnique() JmDNS.makeServiceNameUnique srv collision:" ++ + a ++ + " s.server=" ++ + s.server ++ + " " ++ + localHost.getName() ++ + " equals:" + (s.server.equals(localHost.getName()))); ++ info.setName(incrementName(info.getName())); ++ collision = true; ++ break; ++ } ++ } ++ } ++ ++ // Check for collision with other service infos published by JmDNS ++ final Object selfService = services.get(info.getQualifiedName().toLowerCase()); ++ if (selfService != null && selfService != info) ++ { ++ info.setName(incrementName(info.getName())); ++ collision = true; ++ } ++ } ++ while (collision); ++ ++ return !(originalQualifiedName.equals(info.getQualifiedName())); ++ } ++ ++ String incrementName(String name) ++ { ++ try ++ { ++ final int l = name.lastIndexOf('('); ++ final int r = name.lastIndexOf(')'); ++ if ((l >= 0) && (l < r)) ++ { ++ name = name.substring(0, l) + "(" ++ + (Integer.parseInt(name.substring(l + 1, r)) + 1) + ")"; ++ } ++ else ++ { ++ name += " (2)"; ++ } ++ } ++ catch (final NumberFormatException e) ++ { ++ name += " (2)"; ++ } ++ return name; ++ } ++ ++ /** ++ * Add a listener for a question. The listener will receive updates of ++ * answers to the question as they arrive, or from the cache if they are ++ * already available. ++ */ ++ public void addListener(DNSListener listener, DNSQuestion question) ++ { ++ final long now = System.currentTimeMillis(); ++ ++ // add the new listener ++ synchronized (this) ++ { ++ listeners.add(listener); ++ } ++ ++ // report existing matched records ++ if (question != null) ++ { ++ for (DNSCache.CacheNode i = cache.find(question.name); i != null; i = i.next()) ++ { ++ final DNSRecord c = (DNSRecord) i.getValue(); ++ if (question.answeredBy(c) && !c.isExpired(now)) ++ { ++ listener.updateRecord(this, now, c); ++ } ++ } ++ } ++ } ++ ++ /** ++ * Remove a listener from all outstanding questions. The listener will no ++ * longer receive any updates. ++ */ ++ public void removeListener(DNSListener listener) ++ { ++ synchronized (this) ++ { ++ listeners.remove(listener); ++ } ++ } ++ ++ // Remind: Method updateRecord should receive a better name. ++ /** ++ * Notify all listeners that a record was updated. ++ */ ++ public void updateRecord(long now, DNSRecord rec) ++ { ++ // We do not want to block the entire DNS while we are updating the ++ // record for each listener (service info) ++ List listenerList = null; ++ synchronized (this) ++ { ++ listenerList = new ArrayList(listeners); ++ } ++ for (final Iterator iterator = listenerList.iterator(); iterator.hasNext();) ++ { ++ final DNSListener listener = (DNSListener) iterator.next(); ++ listener.updateRecord(this, now, rec); ++ } ++ if (rec.type == DNSConstants.TYPE_PTR || rec.type == DNSConstants.TYPE_SRV) ++ { ++ List serviceListenerList = null; ++ synchronized (serviceListeners) ++ { ++ serviceListenerList = (List) serviceListeners.get(rec.name.toLowerCase()); ++ // Iterate on a copy in case listeners will modify it ++ if (serviceListenerList != null) ++ { ++ serviceListenerList = new ArrayList(serviceListenerList); ++ } ++ } ++ if (serviceListenerList != null) ++ { ++ final boolean expired = rec.isExpired(now); ++ final String type = rec.getName(); ++ final String name = ((DNSRecord.Pointer) rec).getAlias(); ++ // DNSRecord old = (DNSRecord)services.get(name.toLowerCase()); ++ if (!expired) ++ { ++ // new record ++ final ServiceEvent event = new ServiceEventImpl(this, type, toUnqualifiedName( ++ type, name), null); ++ for (final Iterator iterator = serviceListenerList.iterator(); iterator ++ .hasNext();) ++ { ++ ((ServiceListener) iterator.next()).serviceAdded(event); ++ } ++ } ++ else ++ { ++ // expire record ++ final ServiceEvent event = new ServiceEventImpl(this, type, toUnqualifiedName( ++ type, name), null); ++ for (final Iterator iterator = serviceListenerList.iterator(); iterator ++ .hasNext();) ++ { ++ ((ServiceListener) iterator.next()).serviceRemoved(event); ++ } ++ } ++ } ++ } ++ } ++ ++ /** ++ * Handle an incoming response. Cache answers, and pass them on to the ++ * appropriate questions. ++ */ ++ void handleResponse(DNSIncoming msg) throws IOException ++ { ++ final long now = System.currentTimeMillis(); ++ ++ boolean hostConflictDetected = false; ++ boolean serviceConflictDetected = false; ++ ++ for (final Iterator i = msg.answers.iterator(); i.hasNext();) ++ { ++ boolean isInformative = false; ++ DNSRecord rec = (DNSRecord) i.next(); ++ final boolean expired = rec.isExpired(now); ++ ++ // update the cache ++ synchronized (cache) ++ { ++ final DNSRecord c; ++ ++ // Only store / use ONE TXT record in cache. ++ if (rec instanceof DNSRecord.Text) ++ { ++ c = (DNSRecord) cache.get(rec.getName(), ++ DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN); ++ } ++ else ++ { ++ c = (DNSRecord) cache.get(rec); ++ } ++ ++ if (c != null) ++ { ++ if (expired) ++ { ++ isInformative = true; ++ cache.remove(c); ++ } ++ else ++ { ++ // If a TXT entry is received, if it has changed ++ // update the cache and inform the outside world. ++ if (rec instanceof DNSRecord.Text && ++ !rec.sameValue(c)) ++ { ++ isInformative = true; ++ cache.remove(c); ++ cache.add(rec); ++ } ++ else ++ { ++ c.resetTTL(rec); ++ rec = c; ++ } ++ } ++ } ++ else ++ { ++ if (!expired) ++ { ++ isInformative = true; ++ cache.add(rec); ++ } ++ } ++ } ++ switch (rec.type) ++ { ++ case DNSConstants.TYPE_PTR: ++ // handle _mdns._udp records ++ if (rec.getName().indexOf("._mdns._udp.") >= 0) ++ { ++ if (!expired && rec.name.startsWith("_services._mdns._udp.")) ++ { ++ isInformative = true; ++ registerServiceType(((DNSRecord.Pointer) rec).alias); ++ } ++ continue; ++ } ++ registerServiceType(rec.name); ++ break; ++ } ++ ++ if ((rec.getType() == DNSConstants.TYPE_A) || (rec.getType() == DNSConstants.TYPE_AAAA)) ++ { ++ hostConflictDetected |= rec.handleResponse(this); ++ } ++ else ++ { ++ serviceConflictDetected |= rec.handleResponse(this); ++ } ++ ++ // notify the listeners ++ if (isInformative) ++ { ++ updateRecord(now, rec); ++ } ++ } ++ ++ if (hostConflictDetected || serviceConflictDetected) ++ { ++ new Prober(this).start(timer); ++ } ++ } ++ ++ /** ++ * Handle an incoming query. See if we can answer any part of it given our ++ * service infos. ++ */ ++ void handleQuery(DNSIncoming in, InetAddress addr, int port) throws IOException ++ { ++ // Track known answers ++ boolean hostConflictDetected = false; ++ boolean serviceConflictDetected = false; ++ final long expirationTime = System.currentTimeMillis() + DNSConstants.KNOWN_ANSWER_TTL; ++ for (final Iterator i = in.answers.iterator(); i.hasNext();) ++ { ++ final DNSRecord answer = (DNSRecord) i.next(); ++ if ((answer.getType() == DNSConstants.TYPE_A) ++ || (answer.getType() == DNSConstants.TYPE_AAAA)) ++ { ++ hostConflictDetected |= answer.handleQuery(this, expirationTime); ++ } ++ else ++ { ++ serviceConflictDetected |= answer.handleQuery(this, expirationTime); ++ } ++ } ++ ++ if (plannedAnswer != null) ++ { ++ plannedAnswer.append(in); ++ } ++ else ++ { ++ if (in.isTruncated()) ++ { ++ plannedAnswer = in; ++ } ++ ++ new Responder(this, in, addr, port).start(); ++ } ++ ++ if (hostConflictDetected || serviceConflictDetected) ++ { ++ new Prober(this).start(timer); ++ } ++ } ++ ++ /** ++ * Add an answer to a question. Deal with the case when the outgoing packet ++ * overflows ++ */ ++ public DNSOutgoing addAnswer(DNSIncoming in, InetAddress addr, int port, DNSOutgoing out, ++ DNSRecord rec) throws IOException ++ { ++ if (out == null) ++ { ++ out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); ++ } ++ try ++ { ++ out.addAnswer(in, rec); ++ } ++ catch (final IOException e) ++ { ++ out.flags |= DNSConstants.FLAGS_TC; ++ out.id = in.id; ++ out.finish(); ++ send(out); ++ ++ out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); ++ out.addAnswer(in, rec); ++ } ++ return out; ++ } ++ ++ /** ++ * Send an outgoing multicast DNS message. ++ */ ++ public void send(DNSOutgoing out) throws IOException ++ { ++ out.finish(); ++ if (!out.isEmpty()) ++ { ++ final DatagramPacket packet = new DatagramPacket(out.data, out.off, group, ++ DNSConstants.MDNS_PORT); ++ ++ try ++ { ++ final DNSIncoming msg = new DNSIncoming(packet); ++ logger.finest("send() JmDNS out:" + msg.print(true)); ++ } ++ catch (final IOException e) ++ { ++ logger.throwing(getClass().toString(), ++ "send(DNSOutgoing) - JmDNS can not parse what it sends!!!", e); ++ } ++ final MulticastSocket ms = socket; ++ if (ms != null && !ms.isClosed()) ++ ms.send(packet); ++ } ++ } ++ ++ public void startAnnouncer() ++ { ++ new Announcer(this).start(timer); ++ } ++ ++ public void startTextAnnouncer() ++ { ++ new TextAnnouncer(this).start(timer); ++ } ++ ++ public void startRenewer() ++ { ++ new Renewer(this).start(timer); ++ } ++ ++ public void schedule(TimerTask task, int delay) ++ { ++ timer.schedule(task, delay); ++ } ++ ++ // REMIND: Why is this not an anonymous inner class? ++ /** ++ * Shutdown operations. ++ */ ++ private class Shutdown implements Runnable ++ { ++ public void run() ++ { ++ shutdown = null; ++ close(); ++ } ++ } ++ ++ /** ++ * Recover jmdns when there is an error. ++ */ ++ public void recover() ++ { ++ logger.finer("recover()"); ++ // We have an IO error so lets try to recover if anything happens lets ++ // close it. ++ // This should cover the case of the IP address changing under our feet ++ if (DNSState.CANCELED != getState()) ++ { ++ synchronized (this) ++ { // Synchronize only if we are not already in process to prevent ++ // dead locks ++ // ++ logger.finer("recover() Cleanning up"); ++ // Stop JmDNS ++ setState(DNSState.CANCELED); // This protects against recursive ++ // calls ++ ++ // We need to keep a copy for reregistration ++ final Collection oldServiceInfos = new ArrayList(getServices().values()); ++ ++ // Cancel all services ++ unregisterAllServices(); ++ disposeServiceCollectors(); ++ // ++ // close multicast socket ++ closeMulticastSocket(); ++ // ++ cache.clear(); ++ logger.finer("recover() All is clean"); ++ // ++ // All is clear now start the services ++ // ++ try ++ { ++ openMulticastSocket(getLocalHost()); ++ start(oldServiceInfos); ++ } ++ catch (final Exception exception) ++ { ++ logger.log(Level.WARNING, "recover() Start services exception ", exception); ++ } ++ logger.log(Level.WARNING, "recover() We are back!"); ++ } ++ } ++ } ++ ++ /** ++ * @see org.jmdns.JmDNS#close() ++ */ ++ public void close() ++ { ++ if (getState() != DNSState.CANCELED) ++ { ++ synchronized (this) ++ { // Synchronize only if we are not already in process to prevent ++ // dead locks ++ // Stop JmDNS ++ setState(DNSState.CANCELED); // This protects against recursive ++ // calls ++ ++ unregisterAllServices(); ++ disposeServiceCollectors(); ++ ++ // close socket ++ closeMulticastSocket(); ++ ++ // Stop the timer ++ timer.cancel(); ++ ++ // remove the shutdown hook ++ if (shutdown != null) ++ { ++ Runtime.getRuntime().removeShutdownHook(shutdown); ++ } ++ ++ } ++ } ++ } ++ ++ /** ++ * List cache entries, for debugging only. ++ */ ++ void print() ++ { ++ System.out.println("---- cache ----"); ++ cache.print(); ++ System.out.println(); ++ } ++ ++ /** ++ * @see org.jmdns.JmDNS#printServices() ++ */ ++ public void printServices() ++ { ++ System.err.println(toString()); ++ } ++ ++ public String toString() ++ { ++ final StringBuffer aLog = new StringBuffer(); ++ aLog.append("\t---- Services -----"); ++ if (services != null) ++ { ++ for (final Iterator k = services.keySet().iterator(); k.hasNext();) ++ { ++ final Object key = k.next(); ++ aLog.append("\n\t\tService: " + key + ": " + services.get(key)); ++ } ++ } ++ aLog.append("\n"); ++ aLog.append("\t---- Types ----"); ++ if (serviceTypes != null) ++ { ++ for (final Iterator k = serviceTypes.keySet().iterator(); k.hasNext();) ++ { ++ final Object key = k.next(); ++ aLog.append("\n\t\tType: " + key + ": " + serviceTypes.get(key)); ++ } ++ } ++ aLog.append("\n"); ++ aLog.append(cache.toString()); ++ aLog.append("\n"); ++ aLog.append("\t---- Service Collectors ----"); ++ if (serviceCollectors != null) ++ { ++ synchronized (serviceCollectors) ++ { ++ for (final Iterator k = serviceCollectors.keySet().iterator(); k.hasNext();) ++ { ++ final Object key = k.next(); ++ aLog.append("\n\t\tService Collector: " + key + ": " ++ + serviceCollectors.get(key)); ++ } ++ serviceCollectors.clear(); ++ } ++ } ++ return aLog.toString(); ++ } ++ ++ /** ++ * @see org.jmdns.JmDNS#list(java.lang.String) ++ */ ++ public ServiceInfo[] list(String type) ++ { ++ // Implementation note: The first time a list for a given type is ++ // requested, a ServiceCollector is created which collects service ++ // infos. This greatly speeds up the performance of subsequent calls ++ // to this method. The caveats are, that 1) the first call to this ++ // method ++ // for a given type is slow, and 2) we spawn a ServiceCollector ++ // instance for each service type which increases network traffic a ++ // little. ++ ++ ServiceCollector collector; ++ ++ boolean newCollectorCreated; ++ synchronized (serviceCollectors) ++ { ++ collector = (ServiceCollector) serviceCollectors.get(type); ++ if (collector == null) ++ { ++ collector = new ServiceCollector(type); ++ serviceCollectors.put(type, collector); ++ addServiceListener(type, collector); ++ newCollectorCreated = true; ++ } ++ else ++ { ++ newCollectorCreated = false; ++ } ++ } ++ ++ // After creating a new ServiceCollector, we collect service infos for ++ // 200 milliseconds. This should be enough time, to get some service ++ // infos from the network. ++ if (newCollectorCreated) ++ { ++ try ++ { ++ Thread.sleep(200); ++ } ++ catch (final InterruptedException e) ++ { ++ } ++ } ++ ++ return collector.list(); ++ } ++ ++ /** ++ * This method disposes all ServiceCollector instances which have been ++ * created by calls to method list(type). ++ * ++ * @see #list ++ */ ++ private void disposeServiceCollectors() ++ { ++ logger.finer("disposeServiceCollectors()"); ++ synchronized (serviceCollectors) ++ { ++ for (final Iterator i = serviceCollectors.values().iterator(); i.hasNext();) ++ { ++ final ServiceCollector collector = (ServiceCollector) i.next(); ++ removeServiceListener(collector.type, collector); ++ } ++ serviceCollectors.clear(); ++ } ++ } ++ ++ /** ++ * Instances of ServiceCollector are used internally to speed up the ++ * performance of method list(type). ++ * ++ * @see #list ++ */ ++ private static class ServiceCollector implements ServiceListener ++ { ++ private static Logger logger = Logger.getLogger(ServiceCollector.class.getName()); ++ /** ++ * A set of collected service instance names. ++ */ ++ private final Map infos = Collections.synchronizedMap(new HashMap()); ++ ++ public String type; ++ ++ public ServiceCollector(String type) ++ { ++ this.type = type; ++ } ++ ++ /** ++ * A service has been added. ++ */ ++ public void serviceAdded(ServiceEvent event) ++ { ++ synchronized (infos) ++ { ++ event.getDNS().requestServiceInfo(event.getType(), event.getName(), 0); ++ } ++ } ++ ++ /** ++ * A service has been removed. ++ */ ++ public void serviceRemoved(ServiceEvent event) ++ { ++ synchronized (infos) ++ { ++ infos.remove(event.getName()); ++ } ++ } ++ ++ /** ++ * A service hase been resolved. Its details are now available in the ++ * ServiceInfo record. ++ */ ++ public void serviceResolved(ServiceEvent event) ++ { ++ synchronized (infos) ++ { ++ infos.put(event.getName(), event.getInfo()); ++ } ++ } ++ ++ /** ++ * Returns an array of all service infos which have been collected by ++ * this ServiceCollector. ++ */ ++ public ServiceInfoImpl[] list() ++ { ++ synchronized (infos) ++ { ++ return (ServiceInfoImpl[]) infos.values() ++ .toArray(new ServiceInfoImpl[infos.size()]); ++ } ++ } ++ ++ public String toString() ++ { ++ final StringBuffer aLog = new StringBuffer(); ++ synchronized (infos) ++ { ++ for (final Iterator k = infos.keySet().iterator(); k.hasNext();) ++ { ++ final Object key = k.next(); ++ aLog.append("\n\t\tService: " + key + ": " + infos.get(key)); ++ } ++ } ++ return aLog.toString(); ++ } ++ }; ++ ++ private static String toUnqualifiedName(String type, String qualifiedName) ++ { ++ if (qualifiedName.endsWith(type)) ++ { ++ return qualifiedName.substring(0, qualifiedName.length() - type.length() - 1); ++ } ++ else ++ { ++ return qualifiedName; ++ } ++ } ++ ++ public void setState(DNSState state) ++ { ++ this.state = state; ++ } ++ ++ public void setTask(TimerTask task) ++ { ++ this.task = task; ++ } ++ ++ public TimerTask getTask() ++ { ++ return task; ++ } ++ ++ public Map getServices() ++ { ++ return services; ++ } ++ ++ public void setLastThrottleIncrement(long lastThrottleIncrement) ++ { ++ this.lastThrottleIncrement = lastThrottleIncrement; ++ } ++ ++ public long getLastThrottleIncrement() ++ { ++ return lastThrottleIncrement; ++ } ++ ++ public void setThrottle(int throttle) ++ { ++ this.throttle = throttle; ++ } ++ ++ public int getThrottle() ++ { ++ return throttle; ++ } ++ ++ public static Random getRandom() ++ { ++ return random; ++ } ++ ++ public void setIoLock(Object ioLock) ++ { ++ this.ioLock = ioLock; ++ } ++ ++ public Object getIoLock() ++ { ++ return ioLock; ++ } ++ ++ public void setPlannedAnswer(DNSIncoming plannedAnswer) ++ { ++ this.plannedAnswer = plannedAnswer; ++ } ++ ++ public DNSIncoming getPlannedAnswer() ++ { ++ return plannedAnswer; ++ } ++ ++ void setLocalHost(HostInfo localHost) ++ { ++ this.localHost = localHost; ++ } ++ ++ public Map getServiceTypes() ++ { ++ return serviceTypes; ++ } ++ ++ public void setClosed(boolean closed) ++ { ++ this.closed = closed; ++ } ++ ++ public boolean isClosed() ++ { ++ return closed; ++ } ++ ++ public MulticastSocket getSocket() ++ { ++ return socket; ++ } ++ ++ public InetAddress getGroup() ++ { ++ return group; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/ServiceEventImpl.java b/external/asmack/build/src/trunk/org/jmdns/impl/ServiceEventImpl.java +new file mode 100644 +index 0000000..8d9fe00 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/ServiceEventImpl.java +@@ -0,0 +1,99 @@ ++///Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package org.jmdns.impl; ++ ++import java.util.logging.Logger; ++ ++import org.jmdns.JmDNS; ++import org.jmdns.ServiceEvent; ++import org.jmdns.ServiceInfo; ++ ++/** ++ * ServiceEvent. ++ * ++ * @author Werner Randelshofer, Rick Blair ++ * @version %I%, %G% ++ */ ++public class ServiceEventImpl extends ServiceEvent ++{ ++ private static Logger logger = Logger.getLogger(ServiceEvent.class.getName()); ++ /** ++ * The type name of the service. ++ */ ++ private String type; ++ /** ++ * The instance name of the service. Or null, if the event was ++ * fired to a service type listener. ++ */ ++ private String name; ++ /** ++ * The service info record, or null if the service could be be resolved. ++ * This is also null, if the event was fired to a service type listener. ++ */ ++ private ServiceInfoImpl info; ++ ++ /** ++ * Creates a new instance. ++ * ++ * @param source the JmDNS instance which originated the event. ++ * @param type the type name of the service. ++ * @param name the instance name of the service. ++ * @param info the service info record, or null if the service could be be resolved. ++ */ ++ public ServiceEventImpl(JmDNSImpl source, String type, String name, ServiceInfoImpl info) ++ { ++ super(source); ++ this.type = type; ++ this.name = name; ++ this.info = info; ++ } ++ ++ /** ++ * @see org.jmdns.ServiceEvent#getDNS() ++ */ ++ public JmDNS getDNS() ++ { ++ return (JmDNS) getSource(); ++ } ++ ++ /** ++ * @see org.jmdns.ServiceEvent#getType() ++ */ ++ public String getType() ++ { ++ return type; ++ } ++ ++ /** ++ * @see org.jmdns.ServiceEvent#getName() ++ */ ++ public String getName() ++ { ++ return name; ++ } ++ ++ /** ++ * @see org.jmdns.ServiceEvent#toString() ++ */ ++ public String toString() ++ { ++ StringBuffer buf = new StringBuffer(); ++ buf.append("<" + getClass().getName() + "> "); ++ buf.append(super.toString()); ++ buf.append(" name "); ++ buf.append(getName()); ++ buf.append(" type "); ++ buf.append(getType()); ++ buf.append(" info "); ++ buf.append(getInfo()); ++ return buf.toString(); ++ } ++ ++ public ServiceInfo getInfo() ++ { ++ return info; ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/ServiceInfoImpl.java b/external/asmack/build/src/trunk/org/jmdns/impl/ServiceInfoImpl.java +new file mode 100644 +index 0000000..2a852f0 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/ServiceInfoImpl.java +@@ -0,0 +1,747 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package org.jmdns.impl; ++ ++import java.io.ByteArrayOutputStream; ++import java.io.IOException; ++import java.io.OutputStream; ++import java.net.InetAddress; ++import java.util.Enumeration; ++import java.util.Hashtable; ++import java.util.TimerTask; ++import java.util.Vector; ++import java.util.Iterator; ++import java.util.Set; ++import java.util.concurrent.CopyOnWriteArraySet; ++import java.util.logging.Logger; ++ ++import org.jmdns.ServiceInfo; ++import org.jmdns.ServiceNameListener; ++import org.jmdns.impl.DNSRecord.Pointer; ++import org.jmdns.impl.DNSRecord.Service; ++import org.jmdns.impl.DNSRecord.Text; ++ ++/** ++ * JmDNS service information. ++ * ++ * @version %I%, %G% ++ * @author Arthur van Hoff, Jeff Sonstein, Werner Randelshofer ++ */ ++public class ServiceInfoImpl extends ServiceInfo implements DNSListener ++{ ++ private static Logger logger = Logger.getLogger(ServiceInfoImpl.class.getName()); ++ private JmDNSImpl dns; ++ ++ // State machine ++ /** ++ * The state of this service info. ++ * This is used only for services announced by JmDNS. ++ *

      ++ * For proper handling of concurrency, this variable must be ++ * changed only using methods advanceState(), revertState() and cancel(). ++ */ ++ private DNSState state = DNSState.PROBING_1; ++ ++ /** ++ * Task associated to this service info. ++ * Possible tasks are JmDNS.Prober, JmDNS.Announcer, JmDNS.Responder, ++ * JmDNS.Canceler. ++ */ ++ private TimerTask task; ++ ++ String type; ++ private String name; ++ String server; ++ int port; ++ int weight; ++ int priority; ++ private byte text[]; ++ Hashtable props; ++ InetAddress addr; ++ private boolean handled = false; ++ ++ ++ /** ++ * Service name change listeners. ++ */ ++ private Set serviceNameListeners = new CopyOnWriteArraySet(); ++ ++ /** ++ * @see org.jmdns.ServiceInfo#create(String, String, int, String) ++ */ ++ public ServiceInfoImpl(String type, String name, int port, String text) ++ { ++ this(type, name, port, 0, 0, text); ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#create(String, String, int, int, int, String) ++ */ ++ public ServiceInfoImpl(String type, String name, int port, int weight, int priority, String text) ++ { ++ this(type, name, port, weight, priority, (byte[]) null); ++ try ++ { ++ ByteArrayOutputStream out = new ByteArrayOutputStream(text.length()); ++ writeUTF(out, text); ++ byte [] data = out.toByteArray(); ++ this.setText(new byte[data.length + 1]); ++ this.getText()[0] = (byte) data.length; ++ System.arraycopy(data, 0, this.getText(), 1, data.length); ++ } ++ catch (IOException e) ++ { ++ throw new RuntimeException("unexpected exception: " + e); ++ } ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#create(String, String, int, int, int, Hashtable) ++ */ ++ public ServiceInfoImpl(String type, String name, int port, int weight, int priority, Hashtable props) ++ { ++ this(type, name, port, weight, priority, new byte[0]); ++ if (props != null) ++ { ++ this.setText(props); ++ } ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#create(String, String, int, int, int, byte[]) ++ */ ++ public ServiceInfoImpl(String type, String name, int port, int weight, int priority, byte text[]) ++ { ++ this.type = type; ++ this.name = name; ++ this.port = port; ++ this.weight = weight; ++ this.priority = priority; ++ this.setText(text); ++ } ++ ++ /** ++ * Construct a service record during service discovery. ++ */ ++ ServiceInfoImpl(String type, String name) ++ { ++ if (!type.endsWith(".")) ++ { ++ throw new IllegalArgumentException("type must be fully qualified DNS name ending in '.': " + type); ++ } ++ ++ this.type = type; ++ this.name = name; ++ } ++ ++ /** ++ * During recovery we need to duplicate service info to reregister them ++ */ ++ ServiceInfoImpl(ServiceInfoImpl info) ++ { ++ if (info != null) ++ { ++ this.type = info.type; ++ this.name = info.name; ++ this.port = info.port; ++ this.weight = info.weight; ++ this.priority = info.priority; ++ this.setText(info.getText()); ++ } ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#getType() ++ */ ++ public String getType() ++ { ++ return type; ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#getName() ++ */ ++ public String getName() ++ { ++ return name; ++ } ++ ++ /** ++ * Sets the service instance name. ++ * ++ * @param name unqualified service instance name, such as foobar ++ */ ++ void setName(String name) ++ { ++ String oldName = this.name; ++ this.name = name; ++ ++ if (!oldName.equals(name)) ++ notifyServiceNameChanged(name, oldName); ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#getQualifiedName() ++ */ ++ public String getQualifiedName() ++ { ++ return name + "." + type; ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#getServer() ++ */ ++ public String getServer() ++ { ++ return server; ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#getHostAddress() ++ */ ++ public String getHostAddress() ++ { ++ return (addr != null ? addr.getHostAddress() : ""); ++ } ++ ++ public InetAddress getAddress() ++ { ++ return addr; ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#getInetAddress() ++ */ ++ public InetAddress getInetAddress() ++ { ++ return addr; ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#getPort() ++ */ ++ public int getPort() ++ { ++ return port; ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#getPriority() ++ */ ++ public int getPriority() ++ { ++ return priority; ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#getWeight() ++ */ ++ public int getWeight() ++ { ++ return weight; ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#getTextBytes() ++ */ ++ public byte[] getTextBytes() ++ { ++ return getText(); ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#getTextString() ++ */ ++ public String getTextString() ++ { ++ if ((getText() == null) || (getText().length == 0) || ((getText().length == 1) && (getText()[0] == 0))) ++ { ++ return null; ++ } ++ return readUTF(getText(), 0, getText().length); ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#getURL() ++ */ ++ public String getURL() ++ { ++ return getURL("http"); ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#getURL(java.lang.String) ++ */ ++ public String getURL(String protocol) ++ { ++ String url = protocol + "://" + getHostAddress() + ":" + getPort(); ++ String path = getPropertyString("path"); ++ if (path != null) ++ { ++ if (path.indexOf("://") >= 0) ++ { ++ url = path; ++ } ++ else ++ { ++ url += path.startsWith("/") ? path : "/" + path; ++ } ++ } ++ return url; ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#getPropertyBytes(java.lang.String) ++ */ ++ public synchronized byte[] getPropertyBytes(String name) ++ { ++ return (byte[]) getProperties().get(name); ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#getPropertyString(java.lang.String) ++ */ ++ public synchronized String getPropertyString(String name) ++ { ++ byte data[] = (byte[]) getProperties().get(name); ++ if (data == null) ++ { ++ return null; ++ } ++ if (data == NO_VALUE) ++ { ++ return "true"; ++ } ++ return readUTF(data, 0, data.length); ++ } ++ ++ /** ++ * @see org.jmdns.ServiceInfo#getPropertyNames() ++ */ ++ public Enumeration getPropertyNames() ++ { ++ Hashtable props = getProperties(); ++ return (props != null) ? props.keys() : new Vector().elements(); ++ } ++ ++ /** ++ * Set record as handled. ++ */ ++ void setHandled() ++ { ++ handled = true; ++ } ++ ++ /** ++ * Check if record has been handled. ++ * ++ * @return true if record has been handled. ++ */ ++ boolean isHandled() ++ { ++ return handled; ++ } ++ ++ /** ++ * Write a UTF string with a length to a stream. ++ */ ++ void writeUTF(OutputStream out, String str) throws IOException ++ { ++ for (int i = 0, len = str.length(); i < len; i++) ++ { ++ int c = str.charAt(i); ++ if ((c >= 0x0001) && (c <= 0x007F)) ++ { ++ out.write(c); ++ } ++ else ++ { ++ if (c > 0x07FF) ++ { ++ out.write(0xE0 | ((c >> 12) & 0x0F)); ++ out.write(0x80 | ((c >> 6) & 0x3F)); ++ out.write(0x80 | ((c >> 0) & 0x3F)); ++ } ++ else ++ { ++ out.write(0xC0 | ((c >> 6) & 0x1F)); ++ out.write(0x80 | ((c >> 0) & 0x3F)); ++ } ++ } ++ } ++ } ++ ++ /** ++ * Read data bytes as a UTF stream. ++ */ ++ String readUTF(byte data[], int off, int len) ++ { ++ StringBuffer buf = new StringBuffer(); ++ for (int end = off + len; off < end;) ++ { ++ int ch = data[off++] & 0xFF; ++ switch (ch >> 4) ++ { ++ case 0: ++ case 1: ++ case 2: ++ case 3: ++ case 4: ++ case 5: ++ case 6: ++ case 7: ++ // 0xxxxxxx ++ break; ++ case 12: ++ case 13: ++ if (off >= len) ++ { ++ return null; ++ } ++ // 110x xxxx 10xx xxxx ++ ch = ((ch & 0x1F) << 6) | (data[off++] & 0x3F); ++ break; ++ case 14: ++ if (off + 2 >= len) ++ { ++ return null; ++ } ++ // 1110 xxxx 10xx xxxx 10xx xxxx ++ ch = ((ch & 0x0f) << 12) | ((data[off++] & 0x3F) << 6) | (data[off++] & 0x3F); ++ break; ++ default: ++ if (off + 1 >= len) ++ { ++ return null; ++ } ++ // 10xx xxxx, 1111 xxxx ++ ch = ((ch & 0x3F) << 4) | (data[off++] & 0x0f); ++ break; ++ } ++ buf.append((char) ch); ++ } ++ return buf.toString(); ++ } ++ ++ synchronized Hashtable getProperties() ++ { ++ if ((props == null) && (getText() != null)) ++ { ++ Hashtable props = new Hashtable(); ++ int off = 0; ++ while (off < getText().length) ++ { ++ // length of the next key value pair ++ int len = getText()[off++] & 0xFF; ++ if ((len == 0) || (off + len > getText().length)) ++ { ++ props.clear(); ++ break; ++ } ++ // look for the '=' ++ int i = 0; ++ for (; (i < len) && (getText()[off + i] != '='); i++) ++ { ++ ; ++ } ++ ++ // get the property name ++ String name = readUTF(getText(), off, i); ++ if (name == null) ++ { ++ props.clear(); ++ break; ++ } ++ if (i == len) ++ { ++ props.put(name, NO_VALUE); ++ } ++ else ++ { ++ byte value[] = new byte[len - ++i]; ++ System.arraycopy(getText(), off + i, value, 0, len - i); ++ props.put(name, value); ++ off += len; ++ } ++ } ++ this.props = props; ++ } ++ return props; ++ } ++ ++ /** ++ * JmDNS callback to update a DNS record. ++ */ ++ public void updateRecord(JmDNSImpl jmdns, long now, DNSRecord rec) ++ { ++ if ((rec != null) && !rec.isExpired(now)) ++ { ++ switch (rec.type) ++ { ++ case DNSConstants.TYPE_A: // IPv4 ++ case DNSConstants.TYPE_AAAA: // IPv6 FIXME [PJYF Oct 14 2004] This has not been tested ++ if (rec.name.equals(server)) ++ { ++ addr = ((DNSRecord.Address) rec).getAddress(); ++ ++ } ++ break; ++ case DNSConstants.TYPE_SRV: ++ if (rec.name.equals(getQualifiedName())) ++ { ++ DNSRecord.Service srv = (DNSRecord.Service) rec; ++ server = srv.server; ++ port = srv.port; ++ weight = srv.weight; ++ priority = srv.priority; ++ addr = null; ++ // changed to use getCache() instead - jeffs ++ // updateRecord(jmdns, now, (DNSRecord)jmdns.cache.get(server, TYPE_A, CLASS_IN)); ++ updateRecord(jmdns, now, (DNSRecord) jmdns.getCache().get(server, DNSConstants.TYPE_A, DNSConstants.CLASS_IN)); ++ } ++ break; ++ case DNSConstants.TYPE_TXT: ++ if (rec.name.equals(getQualifiedName())) ++ { ++ DNSRecord.Text txt = (DNSRecord.Text) rec; ++ setText(txt.text); ++ ++ // Only send TXT updates after it's handled the first time ++ if (isHandled()) ++ { ++ getDns().handleNewTXT(this); ++ } ++ } ++ break; ++ } ++ ++ // Future Design Pattern ++ // This is done, to notify the wait loop in method ++ // JmDNS.getServiceInfo(type, name, timeout); ++ // use isHandled / setHandled instead of getDns ++ //if (hasData() && getDns() != null) ++ if (hasData() && !isHandled()) ++ { ++ getDns().handleServiceResolved(this); ++ //setDns(null); ++ setHandled(); ++ } ++ synchronized (this) ++ { ++ notifyAll(); ++ } ++ } ++ } ++ ++ /** ++ * Returns true if the service info is filled with data. ++ */ ++ public boolean hasData() ++ { ++ return server != null && addr != null && getText() != null; ++ } ++ ++ ++ // State machine ++ /** ++ * Sets the state and notifies all objects that wait on the ServiceInfo. ++ */ ++ public synchronized void advanceState() ++ { ++ state = state.advance(); ++ notifyAll(); ++ } ++ ++ /** ++ * Sets the state and notifies all objects that wait on the ServiceInfo. ++ */ ++ synchronized void revertState() ++ { ++ state = state.revert(); ++ notifyAll(); ++ } ++ ++ /** ++ * Sets the state to the first announce state. ++ */ ++ synchronized void setStateAnnounce() ++ { ++ state = DNSState.ANNOUNCING_1; ++ } ++ ++ /** ++ * Sets the state and notifies all objects that wait on the ServiceInfo. ++ */ ++ synchronized void cancel() ++ { ++ state = DNSState.CANCELED; ++ notifyAll(); ++ } ++ ++ /** ++ * Returns the current state of this info. ++ */ ++ public DNSState getState() ++ { ++ return state; ++ } ++ ++ ++ public int hashCode() ++ { ++ return getQualifiedName().hashCode(); ++ } ++ ++ public boolean equals(Object obj) ++ { ++ return (obj instanceof ServiceInfoImpl) && getQualifiedName().equals(((ServiceInfoImpl) obj).getQualifiedName()); ++ } ++ ++ public String getNiceTextString() ++ { ++ StringBuffer buf = new StringBuffer(); ++ for (int i = 0, len = getText().length; i < len; i++) ++ { ++ if (i >= 20) ++ { ++ buf.append("..."); ++ break; ++ } ++ int ch = getText()[i] & 0xFF; ++ if ((ch < ' ') || (ch > 127)) ++ { ++ buf.append("\\0"); ++ buf.append(Integer.toString(ch, 8)); ++ } ++ else ++ { ++ buf.append((char) ch); ++ } ++ } ++ return buf.toString(); ++ } ++ ++ public String toString() ++ { ++ StringBuffer buf = new StringBuffer(); ++ buf.append("service["); ++ buf.append(getQualifiedName()); ++ buf.append(','); ++ buf.append(getAddress()); ++ buf.append(':'); ++ buf.append(port); ++ buf.append(','); ++ buf.append(getNiceTextString()); ++ buf.append(']'); ++ return buf.toString(); ++ } ++ ++ public void addAnswers(DNSOutgoing out, int ttl, HostInfo localHost) throws IOException ++ { ++ out.addAnswer(new Pointer(type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, ttl, ++ getQualifiedName()), 0); ++ out.addAnswer(new Service(getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN|DNSConstants.CLASS_UNIQUE, ++ ttl, priority, weight, port, localHost.getName()), 0); ++ out.addAnswer(new Text(getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN|DNSConstants.CLASS_UNIQUE, ++ ttl, getText()), 0); ++ } ++ ++ public void addTextAnswer(DNSOutgoing out, int ttl) throws IOException ++ { ++ out.addAnswer(new Text(getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN|DNSConstants.CLASS_UNIQUE, ++ ttl, getText()), 0); ++ } ++ ++ public void setTask(TimerTask task) ++ { ++ this.task = task; ++ } ++ ++ public TimerTask getTask() ++ { ++ return task; ++ } ++ ++ public void setText(byte [] text) ++ { ++ this.text = text; ++ } ++ ++ public void setText(Hashtable props) ++ { ++ try ++ { ++ ByteArrayOutputStream out = new ByteArrayOutputStream(256); ++ for (Enumeration e = props.keys(); e.hasMoreElements();) ++ { ++ String key = (String) e.nextElement(); ++ Object val = props.get(key); ++ ByteArrayOutputStream out2 = new ByteArrayOutputStream(100); ++ writeUTF(out2, key); ++ if (val instanceof String) ++ { ++ out2.write('='); ++ writeUTF(out2, (String) val); ++ } ++ else ++ { ++ if (val instanceof byte[]) ++ { ++ out2.write('='); ++ byte[] bval = (byte[]) val; ++ out2.write(bval, 0, bval.length); ++ } ++ else ++ { ++ if (val != NO_VALUE) ++ { ++ throw new IllegalArgumentException("invalid property value: " + val); ++ } ++ } ++ } ++ byte data[] = out2.toByteArray(); ++ out.write(data.length); ++ out.write(data, 0, data.length); ++ } ++ this.setText(out.toByteArray()); ++ } ++ catch (IOException e) ++ { ++ throw new RuntimeException("unexpected exception: " + e); ++ } ++ } ++ ++ public byte [] getText() ++ { ++ return text; ++ } ++ ++ public void setDns(JmDNSImpl dns) ++ { ++ this.dns = dns; ++ } ++ ++ public JmDNSImpl getDns() ++ { ++ return dns; ++ } ++ ++ private void notifyServiceNameChanged(String newName, String oldName) { ++ Iterator i = serviceNameListeners.iterator(); ++ while (i.hasNext()) { ++ ServiceNameListener l = (ServiceNameListener) i.next(); ++ ++ l.serviceNameChanged(newName, oldName); ++ } ++ } ++ ++ public void addServiceNameListener(ServiceNameListener listener) { ++ serviceNameListeners.add(listener); ++ } ++ ++ public void removeServiceNameListener(ServiceNameListener listener) { ++ serviceNameListeners.remove(listener); ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/SocketListener.java b/external/asmack/build/src/trunk/org/jmdns/impl/SocketListener.java +new file mode 100644 +index 0000000..3937d55 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/SocketListener.java +@@ -0,0 +1,87 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package org.jmdns.impl; ++ ++import java.io.IOException; ++import java.net.DatagramPacket; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++/** ++ * Listen for multicast packets. ++ */ ++class SocketListener implements Runnable ++{ ++ static Logger logger = Logger.getLogger(SocketListener.class.getName()); ++ ++ /** ++ * ++ */ ++ private final JmDNSImpl jmDNSImpl; ++ ++ /** ++ * @param jmDNSImpl ++ */ ++ SocketListener(JmDNSImpl jmDNSImpl) ++ { ++ this.jmDNSImpl = jmDNSImpl; ++ } ++ ++ public void run() ++ { ++ try ++ { ++ byte buf[] = new byte[DNSConstants.MAX_MSG_ABSOLUTE]; ++ DatagramPacket packet = new DatagramPacket(buf, buf.length); ++ while (this.jmDNSImpl.getState() != DNSState.CANCELED) ++ { ++ packet.setLength(buf.length); ++ this.jmDNSImpl.getSocket().receive(packet); ++ if (this.jmDNSImpl.getState() == DNSState.CANCELED) ++ { ++ break; ++ } ++ try ++ { ++ if (this.jmDNSImpl.getLocalHost().shouldIgnorePacket(packet)) ++ { ++ continue; ++ } ++ ++ DNSIncoming msg = new DNSIncoming(packet); ++ logger.finest("SocketListener.run() JmDNS in:" + msg.print(true)); ++ ++ synchronized (this.jmDNSImpl.getIoLock()) ++ { ++ if (msg.isQuery()) ++ { ++ if (packet.getPort() != DNSConstants.MDNS_PORT) ++ { ++ this.jmDNSImpl.handleQuery(msg, packet.getAddress(), packet.getPort()); ++ } ++ this.jmDNSImpl.handleQuery(msg, this.jmDNSImpl.getGroup(), DNSConstants.MDNS_PORT); ++ } ++ else ++ { ++ this.jmDNSImpl.handleResponse(msg); ++ } ++ } ++ } ++ catch (IOException e) ++ { ++ logger.log(Level.WARNING, "run() exception ", e); ++ } ++ } ++ } ++ catch (IOException e) ++ { ++ if (this.jmDNSImpl.getState() != DNSState.CANCELED) ++ { ++ logger.log(Level.WARNING, "run() exception ", e); ++ this.jmDNSImpl.recover(); ++ } ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Announcer.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Announcer.java +new file mode 100644 +index 0000000..d8a9db9 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Announcer.java +@@ -0,0 +1,159 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package org.jmdns.impl.tasks; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Timer; ++import java.util.TimerTask; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++import org.jmdns.impl.DNSConstants; ++import org.jmdns.impl.DNSOutgoing; ++import org.jmdns.impl.DNSState; ++import org.jmdns.impl.JmDNSImpl; ++import org.jmdns.impl.ServiceInfoImpl; ++ ++/** ++ * The Announcer sends an accumulated query of all announces, and advances ++ * the state of all serviceInfos, for which it has sent an announce. ++ * The Announcer also sends announcements and advances the state of JmDNS itself. ++ *

      ++ * When the announcer has run two times, it finishes. ++ */ ++public class Announcer extends TimerTask ++{ ++ static Logger logger = Logger.getLogger(Announcer.class.getName()); ++ ++ /** ++ * ++ */ ++ private final JmDNSImpl jmDNSImpl; ++ /** ++ * The state of the announcer. ++ */ ++ DNSState taskState = DNSState.ANNOUNCING_1; ++ ++ public Announcer(JmDNSImpl jmDNSImpl) ++ { ++ this.jmDNSImpl = jmDNSImpl; ++ // Associate host to this, if it needs announcing ++ if (this.jmDNSImpl.getState() == DNSState.ANNOUNCING_1) ++ { ++ this.jmDNSImpl.setTask(this); ++ } ++ // Associate services to this, if they need announcing ++ synchronized (this.jmDNSImpl) ++ { ++ for (Iterator s = this.jmDNSImpl.getServices().values().iterator(); s.hasNext();) ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) s.next(); ++ if (info.getState() == DNSState.ANNOUNCING_1) ++ { ++ info.setTask(this); ++ } ++ } ++ } ++ } ++ ++ public void start(Timer timer) ++ { ++ timer.schedule(this, DNSConstants.ANNOUNCE_WAIT_INTERVAL, DNSConstants.ANNOUNCE_WAIT_INTERVAL); ++ } ++ ++ public boolean cancel() ++ { ++ // Remove association from host to this ++ if (this.jmDNSImpl.getTask() == this) ++ { ++ this.jmDNSImpl.setTask(null); ++ } ++ ++ // Remove associations from services to this ++ synchronized (this.jmDNSImpl) ++ { ++ for (Iterator i = this.jmDNSImpl.getServices().values().iterator(); i.hasNext();) ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) i.next(); ++ if (info.getTask() == this) ++ { ++ info.setTask(null); ++ } ++ } ++ } ++ ++ return super.cancel(); ++ } ++ ++ public void run() ++ { ++ DNSOutgoing out = null; ++ try ++ { ++ // send probes for JmDNS itself ++ if (this.jmDNSImpl.getState() == taskState) ++ { ++ if (out == null) ++ { ++ out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); ++ } ++ this.jmDNSImpl.getLocalHost().addAddressRecords(out, false); ++ this.jmDNSImpl.advanceState(); ++ } ++ // send announces for services ++ // Defensively copy the services into a local list, ++ // to prevent race conditions with methods registerService ++ // and unregisterService. ++ List list; ++ synchronized (this.jmDNSImpl) ++ { ++ list = new ArrayList(this.jmDNSImpl.getServices().values()); ++ } ++ for (Iterator i = list.iterator(); i.hasNext();) ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) i.next(); ++ synchronized (info) ++ { ++ if (info.getState() == taskState && info.getTask() == this) ++ { ++ info.advanceState(); ++ logger.finer("run() JmDNS announcing " + info.getQualifiedName() + " state " + info.getState()); ++ if (out == null) ++ { ++ out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); ++ } ++ info.addAnswers(out, DNSConstants.DNS_TTL, this.jmDNSImpl.getLocalHost()); ++ } ++ } ++ } ++ if (out != null) ++ { ++ logger.finer("run() JmDNS announcing #" + taskState); ++ this.jmDNSImpl.send(out); ++ } ++ else ++ { ++ // If we have nothing to send, another timer taskState ahead ++ // of us has done the job for us. We can cancel. ++ cancel(); ++ } ++ } ++ catch (Throwable e) ++ { ++ logger.log(Level.WARNING, "run() exception ", e); ++ this.jmDNSImpl.recover(); ++ } ++ ++ taskState = taskState.advance(); ++ if (!taskState.isAnnouncing()) ++ { ++ cancel(); ++ ++ this.jmDNSImpl.startRenewer(); ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Canceler.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Canceler.java +new file mode 100644 +index 0000000..a2179a5 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Canceler.java +@@ -0,0 +1,117 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package org.jmdns.impl.tasks; ++ ++import java.util.Collection; ++import java.util.Timer; ++import java.util.TimerTask; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++import org.jmdns.impl.DNSConstants; ++import org.jmdns.impl.DNSOutgoing; ++import org.jmdns.impl.DNSQuestion; ++import org.jmdns.impl.JmDNSImpl; ++import org.jmdns.impl.ServiceInfoImpl; ++ ++/** ++ * The Canceler sends two announces with TTL=0 for the specified services. ++ */ ++public class Canceler extends TimerTask ++{ ++ static Logger logger = Logger.getLogger(Canceler.class.getName()); ++ ++ /** ++ * ++ */ ++ private final JmDNSImpl jmDNSImpl; ++ /** ++ * Counts the number of announces being sent. ++ */ ++ int count = 0; ++ /** ++ * The services that need cancelling. ++ * Note: We have to use a local variable here, because the services ++ * that are canceled, are removed immediately from variable JmDNS.services. ++ */ ++ private ServiceInfoImpl[] infos; ++ /** ++ * We call notifyAll() on the lock object, when we have canceled the ++ * service infos. ++ * This is used by method JmDNS.unregisterService() and ++ * JmDNS.unregisterAllServices, to ensure that the JmDNS ++ * socket stays open until the Canceler has canceled all services. ++ *

      ++ * Note: We need this lock, because ServiceInfos do the transition from ++ * state ANNOUNCED to state CANCELED before we get here. We could get ++ * rid of this lock, if we added a state named CANCELLING to DNSState. ++ */ ++ private Object lock; ++ int ttl = 0; ++ ++ public Canceler(JmDNSImpl jmDNSImpl, ServiceInfoImpl info, Object lock) ++ { ++ this.jmDNSImpl = jmDNSImpl; ++ this.infos = new ServiceInfoImpl[]{info}; ++ this.lock = lock; ++ this.jmDNSImpl.addListener(info, new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN)); ++ } ++ ++ public Canceler(JmDNSImpl jmDNSImpl, ServiceInfoImpl[] infos, Object lock) ++ { ++ this.jmDNSImpl = jmDNSImpl; ++ this.infos = infos; ++ this.lock = lock; ++ } ++ ++ public Canceler(JmDNSImpl jmDNSImpl, Collection infos, Object lock) ++ { ++ this.jmDNSImpl = jmDNSImpl; ++ this.infos = (ServiceInfoImpl[]) infos.toArray(new ServiceInfoImpl[infos.size()]); ++ this.lock = lock; ++ } ++ ++ public void start(Timer timer) ++ { ++ timer.schedule(this, 0, DNSConstants.ANNOUNCE_WAIT_INTERVAL); ++ } ++ ++ public void run() ++ { ++ try ++ { ++ if (++count < 3) ++ { ++ logger.finer("run() JmDNS canceling service"); ++ // announce the service ++ //long now = System.currentTimeMillis(); ++ DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); ++ for (int i = 0; i < infos.length; i++) ++ { ++ ServiceInfoImpl info = infos[i]; ++ info.addAnswers(out, ttl, this.jmDNSImpl.getLocalHost()); ++ ++ this.jmDNSImpl.getLocalHost().addAddressRecords(out, false); ++ } ++ this.jmDNSImpl.send(out); ++ } ++ else ++ { ++ // After three successful announcements, we are finished. ++ synchronized (lock) ++ { ++ this.jmDNSImpl.setClosed(true); ++ lock.notifyAll(); ++ } ++ this.cancel(); ++ } ++ } ++ catch (Throwable e) ++ { ++ logger.log(Level.WARNING, "run() exception ", e); ++ this.jmDNSImpl.recover(); ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Prober.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Prober.java +new file mode 100644 +index 0000000..a8be8e9 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Prober.java +@@ -0,0 +1,197 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package org.jmdns.impl.tasks; ++ ++import java.util.Iterator; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Timer; ++import java.util.TimerTask; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++import org.jmdns.impl.DNSConstants; ++import org.jmdns.impl.DNSOutgoing; ++import org.jmdns.impl.DNSQuestion; ++import org.jmdns.impl.DNSRecord; ++import org.jmdns.impl.DNSState; ++import org.jmdns.impl.JmDNSImpl; ++import org.jmdns.impl.ServiceInfoImpl; ++ ++/** ++ * The Prober sends three consecutive probes for all service infos ++ * that needs probing as well as for the host name. ++ * The state of each service info of the host name is advanced, when a probe has ++ * been sent for it. ++ * When the prober has run three times, it launches an Announcer. ++ *

      ++ * If a conflict during probes occurs, the affected service infos (and affected ++ * host name) are taken away from the prober. This eventually causes the prober ++ * tho cancel itself. ++ */ ++public class Prober extends TimerTask ++{ ++ static Logger logger = Logger.getLogger(Prober.class.getName()); ++ ++ /** ++ * ++ */ ++ private final JmDNSImpl jmDNSImpl; ++ /** ++ * The state of the prober. ++ */ ++ DNSState taskState = DNSState.PROBING_1; ++ ++ public Prober(JmDNSImpl jmDNSImpl) ++ { ++ this.jmDNSImpl = jmDNSImpl; ++ // Associate the host name to this, if it needs probing ++ if (this.jmDNSImpl.getState() == DNSState.PROBING_1) ++ { ++ this.jmDNSImpl.setTask(this); ++ } ++ // Associate services to this, if they need probing ++ synchronized (this.jmDNSImpl) ++ { ++ for (Iterator iterator = this.jmDNSImpl.getServices().values().iterator(); iterator.hasNext();) ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) iterator.next(); ++ if (info.getState() == DNSState.PROBING_1) ++ { ++ info.setTask(this); ++ } ++ } ++ } ++ } ++ ++ ++ public void start(Timer timer) ++ { ++ long now = System.currentTimeMillis(); ++ if (now - this.jmDNSImpl.getLastThrottleIncrement() < DNSConstants.PROBE_THROTTLE_COUNT_INTERVAL) ++ { ++ this.jmDNSImpl.setThrottle(this.jmDNSImpl.getThrottle() + 1); ++ } ++ else ++ { ++ this.jmDNSImpl.setThrottle(1); ++ } ++ this.jmDNSImpl.setLastThrottleIncrement(now); ++ ++ if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED && this.jmDNSImpl.getThrottle() < DNSConstants.PROBE_THROTTLE_COUNT) ++ { ++ timer.schedule(this, JmDNSImpl.getRandom().nextInt(1 + DNSConstants.PROBE_WAIT_INTERVAL), DNSConstants.PROBE_WAIT_INTERVAL); ++ } ++ else ++ { ++ timer.schedule(this, DNSConstants.PROBE_CONFLICT_INTERVAL, DNSConstants.PROBE_CONFLICT_INTERVAL); ++ } ++ } ++ ++ public boolean cancel() ++ { ++ // Remove association from host name to this ++ if (this.jmDNSImpl.getTask() == this) ++ { ++ this.jmDNSImpl.setTask(null); ++ } ++ ++ // Remove associations from services to this ++ synchronized (this.jmDNSImpl) ++ { ++ for (Iterator i = this.jmDNSImpl.getServices().values().iterator(); i.hasNext();) ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) i.next(); ++ if (info.getTask() == this) ++ { ++ info.setTask(null); ++ } ++ } ++ } ++ ++ return super.cancel(); ++ } ++ ++ public void run() ++ { ++ synchronized (this.jmDNSImpl.getIoLock()) ++ { ++ DNSOutgoing out = null; ++ try ++ { ++ // send probes for JmDNS itself ++ if (this.jmDNSImpl.getState() == taskState && this.jmDNSImpl.getTask() == this) ++ { ++ if (out == null) ++ { ++ out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY); ++ } ++ out.addQuestion(new DNSQuestion(this.jmDNSImpl.getLocalHost().getName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN)); ++ ++ this.jmDNSImpl.getLocalHost().addAddressRecords(out, true); ++ this.jmDNSImpl.advanceState(); ++ } ++ // send probes for services ++ // Defensively copy the services into a local list, ++ // to prevent race conditions with methods registerService ++ // and unregisterService. ++ List list; ++ synchronized (this.jmDNSImpl) ++ { ++ list = new LinkedList(this.jmDNSImpl.getServices().values()); ++ } ++ for (Iterator i = list.iterator(); i.hasNext();) ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) i.next(); ++ ++ synchronized (info) ++ { ++ if (info.getState() == taskState && info.getTask() == this) ++ { ++ info.advanceState(); ++ logger.fine("run() JmDNS probing " + info.getQualifiedName() + " state " + info.getState()); ++ if (out == null) ++ { ++ out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY); ++ out.addQuestion(new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN)); ++ } ++ // the "unique" flag should be not set here because these answers haven't been proven unique yet ++ // this means the record will not exactly match the announcement record ++ out.addAuthorativeAnswer(new DNSRecord.Service(info.getQualifiedName(), ++ DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, ++ info.getPriority(), info.getWeight(), info.getPort(), this.jmDNSImpl.getLocalHost().getName())); ++ } ++ } ++ } ++ if (out != null) ++ { ++ logger.finer("run() JmDNS probing #" + taskState); ++ this.jmDNSImpl.send(out); ++ } ++ else ++ { ++ // If we have nothing to send, another timer taskState ahead ++ // of us has done the job for us. We can cancel. ++ cancel(); ++ return; ++ } ++ } ++ catch (Throwable e) ++ { ++ logger.log(Level.WARNING, "run() exception ", e); ++ this.jmDNSImpl.recover(); ++ } ++ ++ taskState = taskState.advance(); ++ if (!taskState.isProbing()) ++ { ++ cancel(); ++ ++ this.jmDNSImpl.startAnnouncer(); ++ } ++ } ++ } ++ ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/RecordReaper.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/RecordReaper.java +new file mode 100644 +index 0000000..75fa85d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/RecordReaper.java +@@ -0,0 +1,83 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package org.jmdns.impl.tasks; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Timer; ++import java.util.TimerTask; ++import java.util.logging.Logger; ++ ++import org.jmdns.impl.DNSCache; ++import org.jmdns.impl.DNSConstants; ++import org.jmdns.impl.DNSRecord; ++import org.jmdns.impl.DNSState; ++import org.jmdns.impl.JmDNSImpl; ++ ++/** ++ * Periodicaly removes expired entries from the cache. ++ */ ++public class RecordReaper extends TimerTask ++{ ++ static Logger logger = Logger.getLogger(RecordReaper.class.getName()); ++ ++ /** ++ * ++ */ ++ private final JmDNSImpl jmDNSImpl; ++ ++ /** ++ * @param jmDNSImpl ++ */ ++ public RecordReaper(JmDNSImpl jmDNSImpl) ++ { ++ this.jmDNSImpl = jmDNSImpl; ++ } ++ ++ public void start(Timer timer) ++ { ++ timer.schedule(this, DNSConstants.RECORD_REAPER_INTERVAL, DNSConstants.RECORD_REAPER_INTERVAL); ++ } ++ ++ public void run() ++ { ++ synchronized (this.jmDNSImpl) ++ { ++ if (this.jmDNSImpl.getState() == DNSState.CANCELED) ++ { ++ return; ++ } ++ logger.finest("run() JmDNS reaping cache"); ++ ++ // Remove expired answers from the cache ++ // ------------------------------------- ++ // To prevent race conditions, we defensively copy all cache ++ // entries into a list. ++ List list = new ArrayList(); ++ synchronized (this.jmDNSImpl.getCache()) ++ { ++ for (Iterator i = this.jmDNSImpl.getCache().iterator(); i.hasNext();) ++ { ++ for (DNSCache.CacheNode n = (DNSCache.CacheNode) i.next(); n != null; n = n.next()) ++ { ++ list.add(n.getValue()); ++ } ++ } ++ } ++ // Now, we remove them. ++ long now = System.currentTimeMillis(); ++ for (Iterator i = list.iterator(); i.hasNext();) ++ { ++ DNSRecord c = (DNSRecord) i.next(); ++ if (c.isExpired(now)) ++ { ++ this.jmDNSImpl.updateRecord(now, c); ++ this.jmDNSImpl.getCache().remove(c); ++ } ++ } ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Renewer.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Renewer.java +new file mode 100644 +index 0000000..13a5e05 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Renewer.java +@@ -0,0 +1,154 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package org.jmdns.impl.tasks; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Timer; ++import java.util.TimerTask; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++import org.jmdns.impl.DNSConstants; ++import org.jmdns.impl.DNSOutgoing; ++import org.jmdns.impl.DNSState; ++import org.jmdns.impl.JmDNSImpl; ++import org.jmdns.impl.ServiceInfoImpl; ++ ++/** ++ * The Renewer is there to send renewal announcment when the record expire for ours infos. ++ */ ++public class Renewer extends TimerTask ++{ ++ static Logger logger = Logger.getLogger(Renewer.class.getName()); ++ ++ /** ++ * ++ */ ++ private final JmDNSImpl jmDNSImpl; ++ /** ++ * The state of the announcer. ++ */ ++ DNSState taskState = DNSState.ANNOUNCED; ++ ++ public Renewer(JmDNSImpl jmDNSImpl) ++ { ++ this.jmDNSImpl = jmDNSImpl; ++ // Associate host to this, if it needs renewal ++ if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED) ++ { ++ this.jmDNSImpl.setTask(this); ++ } ++ // Associate services to this, if they need renewal ++ synchronized (this.jmDNSImpl) ++ { ++ for (Iterator s = this.jmDNSImpl.getServices().values().iterator(); s.hasNext();) ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) s.next(); ++ if (info.getState() == DNSState.ANNOUNCED) ++ { ++ info.setTask(this); ++ } ++ } ++ } ++ } ++ ++ public void start(Timer timer) ++ { ++ timer.schedule(this, DNSConstants.ANNOUNCED_RENEWAL_TTL_INTERVAL, DNSConstants.ANNOUNCED_RENEWAL_TTL_INTERVAL); ++ } ++ ++ public boolean cancel() ++ { ++ // Remove association from host to this ++ if (this.jmDNSImpl.getTask() == this) ++ { ++ this.jmDNSImpl.setTask(null); ++ } ++ ++ // Remove associations from services to this ++ synchronized (this.jmDNSImpl) ++ { ++ for (Iterator i = this.jmDNSImpl.getServices().values().iterator(); i.hasNext();) ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) i.next(); ++ if (info.getTask() == this) ++ { ++ info.setTask(null); ++ } ++ } ++ } ++ ++ return super.cancel(); ++ } ++ ++ public void run() ++ { ++ DNSOutgoing out = null; ++ try ++ { ++ // send probes for JmDNS itself ++ if (this.jmDNSImpl.getState() == taskState) ++ { ++ if (out == null) ++ { ++ out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); ++ } ++ this.jmDNSImpl.getLocalHost().addAddressRecords(out, false); ++ this.jmDNSImpl.advanceState(); ++ } ++ // send announces for services ++ // Defensively copy the services into a local list, ++ // to prevent race conditions with methods registerService ++ // and unregisterService. ++ List list; ++ synchronized (this.jmDNSImpl) ++ { ++ list = new ArrayList(this.jmDNSImpl.getServices().values()); ++ } ++ for (Iterator i = list.iterator(); i.hasNext();) ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) i.next(); ++ synchronized (info) ++ { ++ if (info.getState() == taskState && info.getTask() == this) ++ { ++ info.advanceState(); ++ logger.finer("run() JmDNS announced " + info.getQualifiedName() + " state " + info.getState()); ++ if (out == null) ++ { ++ out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); ++ } ++ info.addAnswers(out, DNSConstants.DNS_TTL, this.jmDNSImpl.getLocalHost()); ++ } ++ } ++ } ++ if (out != null) ++ { ++ logger.finer("run() JmDNS announced"); ++ this.jmDNSImpl.send(out); ++ } ++ else ++ { ++ // If we have nothing to send, another timer taskState ahead ++ // of us has done the job for us. We can cancel. ++ cancel(); ++ } ++ } ++ catch (Throwable e) ++ { ++ logger.log(Level.WARNING, "run() exception ", e); ++ this.jmDNSImpl.recover(); ++ } ++ ++ taskState = taskState.advance(); ++ if (!taskState.isAnnounced()) ++ { ++ cancel(); ++ ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Responder.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Responder.java +new file mode 100644 +index 0000000..af140ff +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Responder.java +@@ -0,0 +1,291 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package org.jmdns.impl.tasks; ++ ++import java.net.InetAddress; ++import java.util.HashSet; ++import java.util.Iterator; ++import java.util.TimerTask; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++import org.jmdns.impl.DNSConstants; ++import org.jmdns.impl.DNSEntry; ++import org.jmdns.impl.DNSIncoming; ++import org.jmdns.impl.DNSOutgoing; ++import org.jmdns.impl.DNSQuestion; ++import org.jmdns.impl.DNSRecord; ++import org.jmdns.impl.DNSState; ++import org.jmdns.impl.JmDNSImpl; ++import org.jmdns.impl.ServiceInfoImpl; ++ ++/** ++ * The Responder sends a single answer for the specified service infos ++ * and for the host name. ++ */ ++public class Responder extends TimerTask ++{ ++ static Logger logger = Logger.getLogger(Responder.class.getName()); ++ ++ /** ++ * ++ */ ++ private final JmDNSImpl jmDNSImpl; ++ private DNSIncoming in; ++ private InetAddress addr; ++ private int port; ++ ++ public Responder(JmDNSImpl jmDNSImpl, DNSIncoming in, InetAddress addr, int port) ++ { ++ this.jmDNSImpl = jmDNSImpl; ++ this.in = in; ++ this.addr = addr; ++ this.port = port; ++ } ++ ++ public void start() ++ { ++ // According to draft-cheshire-dnsext-multicastdns.txt ++ // chapter "8 Responding": ++ // We respond immediately if we know for sure, that we are ++ // the only one who can respond to the query. ++ // In all other cases, we respond within 20-120 ms. ++ // ++ // According to draft-cheshire-dnsext-multicastdns.txt ++ // chapter "7.2 Multi-Packet Known Answer Suppression": ++ // We respond after 20-120 ms if the query is truncated. ++ ++ boolean iAmTheOnlyOne = true; ++ for (Iterator i = in.getQuestions().iterator(); i.hasNext();) ++ { ++ DNSEntry entry = (DNSEntry) i.next(); ++ if (entry instanceof DNSQuestion) ++ { ++ DNSQuestion q = (DNSQuestion) entry; ++ logger.finest("start() question=" + q); ++ iAmTheOnlyOne &= (q.getType() == DNSConstants.TYPE_SRV ++ || q.getType() == DNSConstants.TYPE_TXT ++ || q.getType() == DNSConstants.TYPE_A ++ || q.getType() == DNSConstants.TYPE_AAAA ++ || this.jmDNSImpl.getLocalHost().getName().equalsIgnoreCase(q.getName()) ++ || this.jmDNSImpl.getServices().containsKey(q.getName().toLowerCase())); ++ if (!iAmTheOnlyOne) ++ { ++ break; ++ } ++ } ++ } ++ int delay = (iAmTheOnlyOne && !in.isTruncated()) ? 0 : DNSConstants.RESPONSE_MIN_WAIT_INTERVAL + JmDNSImpl.getRandom().nextInt(DNSConstants.RESPONSE_MAX_WAIT_INTERVAL - DNSConstants.RESPONSE_MIN_WAIT_INTERVAL + 1) - in.elapseSinceArrival(); ++ if (delay < 0) ++ { ++ delay = 0; ++ } ++ logger.finest("start() Responder chosen delay=" + delay); ++ this.jmDNSImpl.schedule(this, delay); ++ } ++ ++ public void run() ++ { ++ synchronized (this.jmDNSImpl.getIoLock()) ++ { ++ if (this.jmDNSImpl.getPlannedAnswer() == in) ++ { ++ this.jmDNSImpl.setPlannedAnswer(null); ++ } ++ ++ // We use these sets to prevent duplicate records ++ // FIXME - This should be moved into DNSOutgoing ++ HashSet questions = new HashSet(); ++ HashSet answers = new HashSet(); ++ ++ ++ if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED) ++ { ++ try ++ { ++ boolean isUnicast = (port != DNSConstants.MDNS_PORT); ++ ++ ++ // Answer questions ++ for (Iterator iterator = in.getQuestions().iterator(); iterator.hasNext();) ++ { ++ DNSEntry entry = (DNSEntry) iterator.next(); ++ if (entry instanceof DNSQuestion) ++ { ++ DNSQuestion q = (DNSQuestion) entry; ++ ++ // for unicast responses the question must be included ++ if (isUnicast) ++ { ++ //out.addQuestion(q); ++ questions.add(q); ++ } ++ ++ int type = q.getType(); ++ if (type == DNSConstants.TYPE_ANY || type == DNSConstants.TYPE_SRV) ++ { // I ama not sure of why there is a special case here [PJYF Oct 15 2004] ++ if (this.jmDNSImpl.getLocalHost().getName().equalsIgnoreCase(q.getName())) ++ { ++ // type = DNSConstants.TYPE_A; ++ DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS4AddressRecord(); ++ if (answer != null) ++ { ++ answers.add(answer); ++ } ++ answer = this.jmDNSImpl.getLocalHost().getDNS6AddressRecord(); ++ if (answer != null) ++ { ++ answers.add(answer); ++ } ++ type = DNSConstants.TYPE_IGNORE; ++ } ++ else ++ { ++ if (this.jmDNSImpl.getServiceTypes().containsKey(q.getName().toLowerCase())) ++ { ++ type = DNSConstants.TYPE_PTR; ++ } ++ } ++ } ++ ++ switch (type) ++ { ++ case DNSConstants.TYPE_A: ++ { ++ // Answer a query for a domain name ++ //out = addAnswer( in, addr, port, out, host ); ++ DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS4AddressRecord(); ++ if (answer != null) ++ { ++ answers.add(answer); ++ } ++ break; ++ } ++ case DNSConstants.TYPE_AAAA: ++ { ++ // Answer a query for a domain name ++ DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS6AddressRecord(); ++ if (answer != null) ++ { ++ answers.add(answer); ++ } ++ break; ++ } ++ case DNSConstants.TYPE_PTR: ++ { ++ // Answer a query for services of a given type ++ ++ // find matching services ++ for (Iterator serviceIterator = this.jmDNSImpl.getServices().values().iterator(); serviceIterator.hasNext();) ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) serviceIterator.next(); ++ if (info.getState() == DNSState.ANNOUNCED) ++ { ++ if (q.getName().equalsIgnoreCase(info.getType())) ++ { ++ DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS4AddressRecord(); ++ if (answer != null) ++ { ++ answers.add(answer); ++ } ++ answer = this.jmDNSImpl.getLocalHost().getDNS6AddressRecord(); ++ if (answer != null) ++ { ++ answers.add(answer); ++ } ++ answers.add(new DNSRecord.Pointer(info.getType(), DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName())); ++ answers.add(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, ++ info.getPriority(), info.getWeight(), info.getPort(), this.jmDNSImpl.getLocalHost().getName())); ++ answers.add(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, ++ info.getText())); ++ } ++ } ++ } ++ if (q.getName().equalsIgnoreCase("_services._mdns._udp.local.")) ++ { ++ for (Iterator serviceTypeIterator = this.jmDNSImpl.getServiceTypes().values().iterator(); serviceTypeIterator.hasNext();) ++ { ++ answers.add(new DNSRecord.Pointer("_services._mdns._udp.local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, (String) serviceTypeIterator.next())); ++ } ++ } ++ break; ++ } ++ case DNSConstants.TYPE_SRV: ++ case DNSConstants.TYPE_ANY: ++ case DNSConstants.TYPE_TXT: ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) this.jmDNSImpl.getServices().get(q.getName().toLowerCase()); ++ if (info != null && info.getState() == DNSState.ANNOUNCED) ++ { ++ DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS4AddressRecord(); ++ if (answer != null) ++ { ++ answers.add(answer); ++ } ++ answer = this.jmDNSImpl.getLocalHost().getDNS6AddressRecord(); ++ if (answer != null) ++ { ++ answers.add(answer); ++ } ++ answers.add(new DNSRecord.Pointer(info.getType(), DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName())); ++ answers.add(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, ++ info.getPriority(), info.getWeight(), info.getPort(), this.jmDNSImpl.getLocalHost().getName())); ++ answers.add(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.getText())); ++ } ++ break; ++ } ++ default : ++ { ++ //System.out.println("JmDNSResponder.unhandled query:"+q); ++ break; ++ } ++ } ++ } ++ } ++ ++ ++ // remove known answers, if the ttl is at least half of ++ // the correct value. (See Draft Cheshire chapter 7.1.). ++ for (Iterator i = in.getAnswers().iterator(); i.hasNext();) ++ { ++ DNSRecord knownAnswer = (DNSRecord) i.next(); ++ if (knownAnswer.getTtl() > DNSConstants.DNS_TTL / 2 && answers.remove(knownAnswer)) ++ { ++ logger.log(Level.FINER, "JmDNS Responder Known Answer Removed"); ++ } ++ } ++ ++ ++ // responde if we have answers ++ if (answers.size() != 0) ++ { ++ logger.finer("run() JmDNS responding"); ++ DNSOutgoing out = null; ++ if (isUnicast) ++ { ++ out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, false); ++ } ++ ++ for (Iterator i = questions.iterator(); i.hasNext();) ++ { ++ out.addQuestion((DNSQuestion) i.next()); ++ } ++ for (Iterator i = answers.iterator(); i.hasNext();) ++ { ++ out = this.jmDNSImpl.addAnswer(in, addr, port, out, (DNSRecord) i.next()); ++ } ++ this.jmDNSImpl.send(out); ++ } ++ this.cancel(); ++ } ++ catch (Throwable e) ++ { ++ logger.log(Level.WARNING, "run() exception ", e); ++ this.jmDNSImpl.close(); ++ } ++ } ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/ServiceInfoResolver.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/ServiceInfoResolver.java +new file mode 100644 +index 0000000..6478803 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/ServiceInfoResolver.java +@@ -0,0 +1,118 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package org.jmdns.impl.tasks; ++ ++import java.util.Timer; ++import java.util.TimerTask; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++import org.jmdns.impl.DNSConstants; ++import org.jmdns.impl.DNSOutgoing; ++import org.jmdns.impl.DNSQuestion; ++import org.jmdns.impl.DNSRecord; ++import org.jmdns.impl.DNSState; ++import org.jmdns.impl.JmDNSImpl; ++import org.jmdns.impl.ServiceInfoImpl; ++ ++/** ++ * The ServiceInfoResolver queries up to three times consecutively for ++ * a service info, and then removes itself from the timer. ++ *

      ++ * The ServiceInfoResolver will run only if JmDNS is in state ANNOUNCED. ++ * REMIND: Prevent having multiple service resolvers for the same info in the ++ * timer queue. ++ */ ++public class ServiceInfoResolver extends TimerTask ++{ ++ static Logger logger = Logger.getLogger(ServiceInfoResolver.class.getName()); ++ ++ /** ++ * ++ */ ++ private final JmDNSImpl jmDNSImpl; ++ /** ++ * Counts the number of queries being sent. ++ */ ++ int count = 0; ++ private ServiceInfoImpl info; ++ private boolean persistent = false; ++ ++ public ServiceInfoResolver(JmDNSImpl jmDNSImpl, ServiceInfoImpl info, boolean persistent) ++ { ++ this.persistent = persistent; ++ this.jmDNSImpl = jmDNSImpl; ++ this.info = info; ++ info.setDns(this.jmDNSImpl); ++ this.jmDNSImpl.addListener(info, new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN)); ++ } ++ ++ public ServiceInfoResolver(JmDNSImpl jmDNSImpl, ServiceInfoImpl info) ++ { ++ this(jmDNSImpl, info, false); ++ } ++ ++ public void start(Timer timer) ++ { ++ timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL); ++ } ++ ++ public void run() ++ { ++ try ++ { ++ if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED) ++ { ++ if (count++ < 3 && !info.hasData()) ++ { ++ long now = System.currentTimeMillis(); ++ DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY); ++ out.addQuestion(new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN)); ++ out.addQuestion(new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN)); ++ if (info.getServer() != null) ++ { ++ out.addQuestion(new DNSQuestion(info.getServer(), DNSConstants.TYPE_A, DNSConstants.CLASS_IN)); ++ } ++ out.addAnswer((DNSRecord) this.jmDNSImpl.getCache().get(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN), now); ++ out.addAnswer((DNSRecord) this.jmDNSImpl.getCache().get(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN), now); ++ if (info.getServer() != null) ++ { ++ out.addAnswer((DNSRecord) this.jmDNSImpl.getCache().get(info.getServer(), DNSConstants.TYPE_A, DNSConstants.CLASS_IN), now); ++ } ++ this.jmDNSImpl.send(out); ++ } ++ else ++ { ++ // After three queries, we can quit. ++ this.cancel(); ++ ++ // if the persistent flag is on, keep listen for packets ++ if (!isPersistent()) ++ { ++ this.jmDNSImpl.removeListener(info); ++ } ++ } ++ } ++ else ++ { ++ if (this.jmDNSImpl.getState() == DNSState.CANCELED) ++ { ++ this.cancel(); ++ this.jmDNSImpl.removeListener(info); ++ } ++ } ++ } ++ catch (Throwable e) ++ { ++ logger.log(Level.WARNING, "run() exception ", e); ++ this.jmDNSImpl.recover(); ++ } ++ } ++ ++ private boolean isPersistent() ++ { ++ return persistent; ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/ServiceResolver.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/ServiceResolver.java +new file mode 100644 +index 0000000..570ab88 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/ServiceResolver.java +@@ -0,0 +1,101 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package org.jmdns.impl.tasks; ++ ++import java.io.IOException; ++import java.util.Iterator; ++import java.util.Timer; ++import java.util.TimerTask; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++import org.jmdns.impl.DNSConstants; ++import org.jmdns.impl.DNSOutgoing; ++import org.jmdns.impl.DNSQuestion; ++import org.jmdns.impl.DNSRecord; ++import org.jmdns.impl.DNSState; ++import org.jmdns.impl.JmDNSImpl; ++import org.jmdns.impl.ServiceInfoImpl; ++ ++/** ++ * The ServiceResolver queries three times consecutively for services of ++ * a given type, and then removes itself from the timer. ++ *

      ++ * The ServiceResolver will run only if JmDNS is in state ANNOUNCED. ++ * REMIND: Prevent having multiple service resolvers for the same type in the ++ * timer queue. ++ */ ++public class ServiceResolver extends TimerTask ++{ ++ static Logger logger = Logger.getLogger(ServiceResolver.class.getName()); ++ ++ /** ++ * ++ */ ++ private final JmDNSImpl jmDNSImpl; ++ /** ++ * Counts the number of queries being sent. ++ */ ++ int count = 0; ++ private String type; ++ ++ public ServiceResolver(JmDNSImpl jmDNSImpl, String type) ++ { ++ this.jmDNSImpl = jmDNSImpl; ++ this.type = type; ++ } ++ ++ public void start(Timer timer) ++ { ++ timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL); ++ } ++ ++ public void run() ++ { ++ try ++ { ++ if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED) ++ { ++ if (count++ < 3) ++ { ++ logger.finer("run() JmDNS querying service"); ++ long now = System.currentTimeMillis(); ++ DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY); ++ out.addQuestion(new DNSQuestion(type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN)); ++ for (Iterator s = this.jmDNSImpl.getServices().values().iterator(); s.hasNext();) ++ { ++ final ServiceInfoImpl info = (ServiceInfoImpl) s.next(); ++ try ++ { ++ out.addAnswer(new DNSRecord.Pointer(info.getType(), DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()), now); ++ } ++ catch (IOException ee) ++ { ++ break; ++ } ++ } ++ this.jmDNSImpl.send(out); ++ } ++ else ++ { ++ // After three queries, we can quit. ++ this.cancel(); ++ } ++ } ++ else ++ { ++ if (this.jmDNSImpl.getState() == DNSState.CANCELED) ++ { ++ this.cancel(); ++ } ++ } ++ } ++ catch (Throwable e) ++ { ++ logger.log(Level.WARNING, "run() exception ", e); ++ this.jmDNSImpl.recover(); ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/TextAnnouncer.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/TextAnnouncer.java +new file mode 100644 +index 0000000..0dc8902 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/TextAnnouncer.java +@@ -0,0 +1,148 @@ ++//Copyright 2008-2009 Jonas Ã…dahl ++//Licensed under Apache License version 2.0 ++ ++package org.jmdns.impl.tasks; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Timer; ++import java.util.TimerTask; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++import org.jmdns.impl.DNSConstants; ++import org.jmdns.impl.DNSOutgoing; ++import org.jmdns.impl.DNSState; ++import org.jmdns.impl.JmDNSImpl; ++import org.jmdns.impl.ServiceInfoImpl; ++ ++/** ++ * The TextAnnouncer sends an accumulated query of all announces, and advances ++ * the state of all serviceInfos, for which it has sent an announce. ++ * The TextAnnouncer also sends announcements and advances the state of JmDNS itself. ++ *

      ++ * When the announcer has run two times, it finishes. ++ */ ++public class TextAnnouncer extends TimerTask ++{ ++ static Logger logger = Logger.getLogger(TextAnnouncer.class.getName()); ++ ++ /** ++ * ++ */ ++ private final JmDNSImpl jmDNSImpl; ++ /** ++ * The state of the announcer. ++ */ ++ DNSState taskState = DNSState.ANNOUNCING_1; ++ ++ public TextAnnouncer(JmDNSImpl jmDNSImpl) ++ { ++ this.jmDNSImpl = jmDNSImpl; ++ // Associate host to this, if it needs announcing ++ if (this.jmDNSImpl.getState() == DNSState.ANNOUNCING_1) ++ { ++ this.jmDNSImpl.setTask(this); ++ } ++ // Associate services to this, if they need announcing ++ synchronized (this.jmDNSImpl) ++ { ++ for (Iterator s = this.jmDNSImpl.getServices().values().iterator(); s.hasNext();) ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) s.next(); ++ if (info.getState() == DNSState.ANNOUNCING_1) ++ { ++ info.setTask(this); ++ } ++ } ++ } ++ } ++ ++ public void start(Timer timer) ++ { ++ timer.schedule(this, DNSConstants.ANNOUNCE_WAIT_INTERVAL, DNSConstants.ANNOUNCE_WAIT_INTERVAL); ++ } ++ ++ public boolean cancel() ++ { ++ // Remove association from host to this ++ if (this.jmDNSImpl.getTask() == this) ++ { ++ this.jmDNSImpl.setTask(null); ++ } ++ ++ // Remove associations from services to this ++ synchronized (this.jmDNSImpl) ++ { ++ for (Iterator i = this.jmDNSImpl.getServices().values().iterator(); i.hasNext();) ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) i.next(); ++ if (info.getTask() == this) ++ { ++ info.setTask(null); ++ } ++ } ++ } ++ ++ return super.cancel(); ++ } ++ ++ public void run() ++ { ++ DNSOutgoing out = null; ++ try ++ { ++ // send announces for services ++ // Defensively copy the services into a local list, ++ // to prevent race conditions with methods registerService ++ // and unregisterService. ++ List list; ++ synchronized (this.jmDNSImpl) ++ { ++ list = new ArrayList(this.jmDNSImpl.getServices().values()); ++ } ++ for (Iterator i = list.iterator(); i.hasNext();) ++ { ++ ServiceInfoImpl info = (ServiceInfoImpl) i.next(); ++ synchronized (info) ++ { ++ if (info.getState().isAnnouncing() && info.getTask() == this) ++ { ++ info.advanceState(); ++ logger.finer("run() JmDNS announcing " + info.getQualifiedName() + " state " + info.getState()); ++ if (out == null) ++ { ++ out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); ++ } ++ info.addTextAnswer(out, DNSConstants.DNS_TTL); ++ } ++ } ++ } ++ if (out != null) ++ { ++ logger.finer("run() JmDNS announcing #" + taskState); ++ this.jmDNSImpl.send(out); ++ } ++ else ++ { ++ // If we have nothing to send, another timer taskState ahead ++ // of us has done the job for us. We can cancel. ++ cancel(); ++ } ++ } ++ catch (Throwable e) ++ { ++ logger.log(Level.WARNING, "run() exception ", e); ++ this.jmDNSImpl.recover(); ++ } ++ ++ taskState = taskState.advance(); ++ if (!taskState.isAnnouncing()) ++ { ++ cancel(); ++ ++ this.jmDNSImpl.startRenewer(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/TypeResolver.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/TypeResolver.java +new file mode 100644 +index 0000000..d0e7c29 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/TypeResolver.java +@@ -0,0 +1,92 @@ ++//Copyright 2003-2005 Arthur van Hoff, Rick Blair ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package org.jmdns.impl.tasks; ++ ++import java.util.Iterator; ++import java.util.Timer; ++import java.util.TimerTask; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++import org.jmdns.impl.DNSConstants; ++import org.jmdns.impl.DNSOutgoing; ++import org.jmdns.impl.DNSQuestion; ++import org.jmdns.impl.DNSRecord; ++import org.jmdns.impl.DNSState; ++import org.jmdns.impl.JmDNSImpl; ++ ++/** ++ * Helper class to resolve service types. ++ *

      ++ * The TypeResolver queries three times consecutively for service types, and then ++ * removes itself from the timer. ++ *

      ++ * The TypeResolver will run only if JmDNS is in state ANNOUNCED. ++ */ ++public class TypeResolver extends TimerTask ++{ ++ static Logger logger = Logger.getLogger(TypeResolver.class.getName()); ++ ++ /** ++ * ++ */ ++ private final JmDNSImpl jmDNSImpl; ++ ++ /** ++ * @param jmDNSImpl ++ */ ++ public TypeResolver(JmDNSImpl jmDNSImpl) ++ { ++ this.jmDNSImpl = jmDNSImpl; ++ } ++ ++ public void start(Timer timer) ++ { ++ timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL); ++ } ++ ++ /** ++ * Counts the number of queries that were sent. ++ */ ++ int count = 0; ++ ++ public void run() ++ { ++ try ++ { ++ if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED) ++ { ++ if (count++ < 3) ++ { ++ logger.finer("run() JmDNS querying type"); ++ DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY); ++ out.addQuestion(new DNSQuestion("_services._mdns._udp.local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN)); ++ for (Iterator iterator = this.jmDNSImpl.getServiceTypes().values().iterator(); iterator.hasNext();) ++ { ++ out.addAnswer(new DNSRecord.Pointer("_services._mdns._udp.local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, (String) iterator.next()), 0); ++ } ++ this.jmDNSImpl.send(out); ++ } ++ else ++ { ++ // After three queries, we can quit. ++ this.cancel(); ++ } ++ } ++ else ++ { ++ if (this.jmDNSImpl.getState() == DNSState.CANCELED) ++ { ++ this.cancel(); ++ } ++ } ++ } ++ catch (Throwable e) ++ { ++ logger.log(Level.WARNING, "run() exception ", e); ++ this.jmDNSImpl.recover(); ++ } ++ } ++} +\ No newline at end of file +diff --git a/external/asmack/build/src/trunk/org/jmdns/package.html b/external/asmack/build/src/trunk/org/jmdns/package.html +new file mode 100644 +index 0000000..b02f16a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/jmdns/package.html +@@ -0,0 +1,47 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++This is an implementation of a multi-cast DNS in Java. It currently ++supports service discovery and service registration. It is fully ++interoperable with Apple's Rendezvous. ++ ++ ++ ++ ++

      Related Documentation

      ++ ++ ++For overviews, tutorials, examples, guides, and tool documentation, please see ++the Readme file of JmDNS. ++ ++ ++ ++ ++ +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/A6Record.java b/external/asmack/build/src/trunk/org/xbill/DNS/A6Record.java +new file mode 100644 +index 0000000..a1c613a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/A6Record.java +@@ -0,0 +1,127 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.net.*; ++ ++/** ++ * A6 Record - maps a domain name to an IPv6 address (experimental) ++ * ++ * @author Brian Wellington ++ */ ++ ++public class A6Record extends Record { ++ ++private static final long serialVersionUID = -8815026887337346789L; ++ ++private int prefixBits; ++private InetAddress suffix; ++private Name prefix; ++ ++A6Record() {} ++ ++Record ++getObject() { ++ return new A6Record(); ++} ++ ++/** ++ * Creates an A6 Record from the given data ++ * @param prefixBits The number of bits in the address prefix ++ * @param suffix The address suffix ++ * @param prefix The name of the prefix ++ */ ++public ++A6Record(Name name, int dclass, long ttl, int prefixBits, ++ InetAddress suffix, Name prefix) ++{ ++ super(name, Type.A6, dclass, ttl); ++ this.prefixBits = checkU8("prefixBits", prefixBits); ++ if (suffix != null && Address.familyOf(suffix) != Address.IPv6) ++ throw new IllegalArgumentException("invalid IPv6 address"); ++ this.suffix = suffix; ++ if (prefix != null) ++ this.prefix = checkName("prefix", prefix); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ prefixBits = in.readU8(); ++ int suffixbits = 128 - prefixBits; ++ int suffixbytes = (suffixbits + 7) / 8; ++ if (prefixBits < 128) { ++ byte [] bytes = new byte[16]; ++ in.readByteArray(bytes, 16 - suffixbytes, suffixbytes); ++ suffix = InetAddress.getByAddress(bytes); ++ } ++ if (prefixBits > 0) ++ prefix = new Name(in); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ prefixBits = st.getUInt8(); ++ if (prefixBits > 128) { ++ throw st.exception("prefix bits must be [0..128]"); ++ } else if (prefixBits < 128) { ++ String s = st.getString(); ++ try { ++ suffix = Address.getByAddress(s, Address.IPv6); ++ } ++ catch (UnknownHostException e) { ++ throw st.exception("invalid IPv6 address: " + s); ++ } ++ } ++ if (prefixBits > 0) ++ prefix = st.getName(origin); ++} ++ ++/** Converts rdata to a String */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(prefixBits); ++ if (suffix != null) { ++ sb.append(" "); ++ sb.append(suffix.getHostAddress()); ++ } ++ if (prefix != null) { ++ sb.append(" "); ++ sb.append(prefix); ++ } ++ return sb.toString(); ++} ++ ++/** Returns the number of bits in the prefix */ ++public int ++getPrefixBits() { ++ return prefixBits; ++} ++ ++/** Returns the address suffix */ ++public InetAddress ++getSuffix() { ++ return suffix; ++} ++ ++/** Returns the address prefix */ ++public Name ++getPrefix() { ++ return prefix; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeU8(prefixBits); ++ if (suffix != null) { ++ int suffixbits = 128 - prefixBits; ++ int suffixbytes = (suffixbits + 7) / 8; ++ byte [] data = suffix.getAddress(); ++ out.writeByteArray(data, 16 - suffixbytes, suffixbytes); ++ } ++ if (prefix != null) ++ prefix.toWire(out, null, canonical); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/AAAARecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/AAAARecord.java +new file mode 100644 +index 0000000..eb98ea9 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/AAAARecord.java +@@ -0,0 +1,66 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.net.*; ++ ++/** ++ * IPv6 Address Record - maps a domain name to an IPv6 address ++ * ++ * @author Brian Wellington ++ */ ++ ++public class AAAARecord extends Record { ++ ++private static final long serialVersionUID = -4588601512069748050L; ++ ++private InetAddress address; ++ ++AAAARecord() {} ++ ++Record ++getObject() { ++ return new AAAARecord(); ++} ++ ++/** ++ * Creates an AAAA Record from the given data ++ * @param address The address suffix ++ */ ++public ++AAAARecord(Name name, int dclass, long ttl, InetAddress address) { ++ super(name, Type.AAAA, dclass, ttl); ++ if (Address.familyOf(address) != Address.IPv6) ++ throw new IllegalArgumentException("invalid IPv6 address"); ++ this.address = address; ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ address = InetAddress.getByAddress(in.readByteArray(16)); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ address = st.getAddress(Address.IPv6); ++} ++ ++/** Converts rdata to a String */ ++String ++rrToString() { ++ return address.getHostAddress(); ++} ++ ++/** Returns the address */ ++public InetAddress ++getAddress() { ++ return address; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeByteArray(address.getAddress()); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/AFSDBRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/AFSDBRecord.java +new file mode 100644 +index 0000000..4814faa +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/AFSDBRecord.java +@@ -0,0 +1,46 @@ ++// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * AFS Data Base Record - maps a domain name to the name of an AFS cell ++ * database server. ++ * ++ * ++ * @author Brian Wellington ++ */ ++ ++public class AFSDBRecord extends U16NameBase { ++ ++private static final long serialVersionUID = 3034379930729102437L; ++ ++AFSDBRecord() {} ++ ++Record ++getObject() { ++ return new AFSDBRecord(); ++} ++ ++/** ++ * Creates an AFSDB Record from the given data. ++ * @param subtype Indicates the type of service provided by the host. ++ * @param host The host providing the service. ++ */ ++public ++AFSDBRecord(Name name, int dclass, long ttl, int subtype, Name host) { ++ super(name, Type.AFSDB, dclass, ttl, subtype, "subtype", host, "host"); ++} ++ ++/** Gets the subtype indicating the service provided by the host. */ ++public int ++getSubtype() { ++ return getU16Field(); ++} ++ ++/** Gets the host providing service for the domain. */ ++public Name ++getHost() { ++ return getNameField(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/APLRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/APLRecord.java +new file mode 100644 +index 0000000..5940da2 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/APLRecord.java +@@ -0,0 +1,287 @@ ++// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.net.*; ++import java.util.*; ++import org.xbill.DNS.utils.*; ++ ++/** ++ * APL - Address Prefix List. See RFC 3123. ++ * ++ * @author Brian Wellington ++ */ ++ ++/* ++ * Note: this currently uses the same constants as the Address class; ++ * this could change if more constants are defined for APL records. ++ */ ++ ++public class APLRecord extends Record { ++ ++public static class Element { ++ public final int family; ++ public final boolean negative; ++ public final int prefixLength; ++ public final Object address; ++ ++ private ++ Element(int family, boolean negative, Object address, int prefixLength) ++ { ++ this.family = family; ++ this.negative = negative; ++ this.address = address; ++ this.prefixLength = prefixLength; ++ if (!validatePrefixLength(family, prefixLength)) { ++ throw new IllegalArgumentException("invalid prefix " + ++ "length"); ++ } ++ } ++ ++ /** ++ * Creates an APL element corresponding to an IPv4 or IPv6 prefix. ++ * @param negative Indicates if this prefix is a negation. ++ * @param address The IPv4 or IPv6 address. ++ * @param prefixLength The length of this prefix, in bits. ++ * @throws IllegalArgumentException The prefix length is invalid. ++ */ ++ public ++ Element(boolean negative, InetAddress address, int prefixLength) { ++ this(Address.familyOf(address), negative, address, ++ prefixLength); ++ } ++ ++ public String ++ toString() { ++ StringBuffer sb = new StringBuffer(); ++ if (negative) ++ sb.append("!"); ++ sb.append(family); ++ sb.append(":"); ++ if (family == Address.IPv4 || family == Address.IPv6) ++ sb.append(((InetAddress) address).getHostAddress()); ++ else ++ sb.append(base16.toString((byte []) address)); ++ sb.append("/"); ++ sb.append(prefixLength); ++ return sb.toString(); ++ } ++ ++ public boolean ++ equals(Object arg) { ++ if (arg == null || !(arg instanceof Element)) ++ return false; ++ Element elt = (Element) arg; ++ return (family == elt.family && ++ negative == elt.negative && ++ prefixLength == elt.prefixLength && ++ address.equals(elt.address)); ++ } ++ ++ public int ++ hashCode() { ++ return address.hashCode() + prefixLength + (negative ? 1 : 0); ++ } ++} ++ ++private static final long serialVersionUID = -1348173791712935864L; ++ ++private List elements; ++ ++APLRecord() {} ++ ++Record ++getObject() { ++ return new APLRecord(); ++} ++ ++private static boolean ++validatePrefixLength(int family, int prefixLength) { ++ if (prefixLength < 0 || prefixLength >= 256) ++ return false; ++ if ((family == Address.IPv4 && prefixLength > 32) || ++ (family == Address.IPv6 && prefixLength > 128)) ++ return false; ++ return true; ++} ++ ++/** ++ * Creates an APL Record from the given data. ++ * @param elements The list of APL elements. ++ */ ++public ++APLRecord(Name name, int dclass, long ttl, List elements) { ++ super(name, Type.APL, dclass, ttl); ++ this.elements = new ArrayList(elements.size()); ++ for (Iterator it = elements.iterator(); it.hasNext(); ) { ++ Object o = it.next(); ++ if (!(o instanceof Element)) { ++ throw new IllegalArgumentException("illegal element"); ++ } ++ Element element = (Element) o; ++ if (element.family != Address.IPv4 && ++ element.family != Address.IPv6) ++ { ++ throw new IllegalArgumentException("unknown family"); ++ } ++ this.elements.add(element); ++ ++ } ++} ++ ++private static byte [] ++parseAddress(byte [] in, int length) throws WireParseException { ++ if (in.length > length) ++ throw new WireParseException("invalid address length"); ++ if (in.length == length) ++ return in; ++ byte [] out = new byte[length]; ++ System.arraycopy(in, 0, out, 0, in.length); ++ return out; ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ elements = new ArrayList(1); ++ while (in.remaining() != 0) { ++ int family = in.readU16(); ++ int prefix = in.readU8(); ++ int length = in.readU8(); ++ boolean negative = (length & 0x80) != 0; ++ length &= ~0x80; ++ ++ byte [] data = in.readByteArray(length); ++ Element element; ++ if (!validatePrefixLength(family, prefix)) { ++ throw new WireParseException("invalid prefix length"); ++ } ++ ++ if (family == Address.IPv4 || family == Address.IPv6) { ++ data = parseAddress(data, ++ Address.addressLength(family)); ++ InetAddress addr = InetAddress.getByAddress(data); ++ element = new Element(negative, addr, prefix); ++ } else { ++ element = new Element(family, negative, data, prefix); ++ } ++ elements.add(element); ++ ++ } ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ elements = new ArrayList(1); ++ while (true) { ++ Tokenizer.Token t = st.get(); ++ if (!t.isString()) ++ break; ++ ++ boolean negative = false; ++ int family = 0; ++ int prefix = 0; ++ ++ String s = t.value; ++ int start = 0; ++ if (s.startsWith("!")) { ++ negative = true; ++ start = 1; ++ } ++ int colon = s.indexOf(':', start); ++ if (colon < 0) ++ throw st.exception("invalid address prefix element"); ++ int slash = s.indexOf('/', colon); ++ if (slash < 0) ++ throw st.exception("invalid address prefix element"); ++ ++ String familyString = s.substring(start, colon); ++ String addressString = s.substring(colon + 1, slash); ++ String prefixString = s.substring(slash + 1); ++ ++ try { ++ family = Integer.parseInt(familyString); ++ } ++ catch (NumberFormatException e) { ++ throw st.exception("invalid family"); ++ } ++ if (family != Address.IPv4 && family != Address.IPv6) ++ throw st.exception("unknown family"); ++ ++ try { ++ prefix = Integer.parseInt(prefixString); ++ } ++ catch (NumberFormatException e) { ++ throw st.exception("invalid prefix length"); ++ } ++ ++ if (!validatePrefixLength(family, prefix)) { ++ throw st.exception("invalid prefix length"); ++ } ++ ++ byte [] bytes = Address.toByteArray(addressString, family); ++ if (bytes == null) ++ throw st.exception("invalid IP address " + ++ addressString); ++ ++ InetAddress address = InetAddress.getByAddress(bytes); ++ elements.add(new Element(negative, address, prefix)); ++ } ++ st.unget(); ++} ++ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ for (Iterator it = elements.iterator(); it.hasNext(); ) { ++ Element element = (Element) it.next(); ++ sb.append(element); ++ if (it.hasNext()) ++ sb.append(" "); ++ } ++ return sb.toString(); ++} ++ ++/** Returns the list of APL elements. */ ++public List ++getElements() { ++ return elements; ++} ++ ++private static int ++addressLength(byte [] addr) { ++ for (int i = addr.length - 1; i >= 0; i--) { ++ if (addr[i] != 0) ++ return i + 1; ++ } ++ return 0; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ for (Iterator it = elements.iterator(); it.hasNext(); ) { ++ Element element = (Element) it.next(); ++ int length = 0; ++ byte [] data; ++ if (element.family == Address.IPv4 || ++ element.family == Address.IPv6) ++ { ++ InetAddress addr = (InetAddress) element.address; ++ data = addr.getAddress(); ++ length = addressLength(data); ++ } else { ++ data = (byte []) element.address; ++ length = data.length; ++ } ++ int wlength = length; ++ if (element.negative) { ++ wlength |= 0x80; ++ } ++ out.writeU16(element.family); ++ out.writeU8(element.prefixLength); ++ out.writeU8(wlength); ++ out.writeByteArray(data, 0, length); ++ } ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ARecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/ARecord.java +new file mode 100644 +index 0000000..7911ea9 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/ARecord.java +@@ -0,0 +1,89 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.net.*; ++import java.io.*; ++ ++/** ++ * Address Record - maps a domain name to an Internet address ++ * ++ * @author Brian Wellington ++ */ ++ ++public class ARecord extends Record { ++ ++private static final long serialVersionUID = -2172609200849142323L; ++ ++private int addr; ++ ++ARecord() {} ++ ++Record ++getObject() { ++ return new ARecord(); ++} ++ ++private static final int ++fromArray(byte [] array) { ++ return (((array[0] & 0xFF) << 24) | ++ ((array[1] & 0xFF) << 16) | ++ ((array[2] & 0xFF) << 8) | ++ (array[3] & 0xFF)); ++} ++ ++private static final byte [] ++toArray(int addr) { ++ byte [] bytes = new byte[4]; ++ bytes[0] = (byte) ((addr >>> 24) & 0xFF); ++ bytes[1] = (byte) ((addr >>> 16) & 0xFF); ++ bytes[2] = (byte) ((addr >>> 8) & 0xFF); ++ bytes[3] = (byte) (addr & 0xFF); ++ return bytes; ++} ++ ++/** ++ * Creates an A Record from the given data ++ * @param address The address that the name refers to ++ */ ++public ++ARecord(Name name, int dclass, long ttl, InetAddress address) { ++ super(name, Type.A, dclass, ttl); ++ if (Address.familyOf(address) != Address.IPv4) ++ throw new IllegalArgumentException("invalid IPv4 address"); ++ addr = fromArray(address.getAddress()); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ addr = fromArray(in.readByteArray(4)); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ InetAddress address = st.getAddress(Address.IPv4); ++ addr = fromArray(address.getAddress()); ++} ++ ++/** Converts rdata to a String */ ++String ++rrToString() { ++ return (Address.toDottedQuad(toArray(addr))); ++} ++ ++/** Returns the Internet address */ ++public InetAddress ++getAddress() { ++ try { ++ return InetAddress.getByAddress(toArray(addr)); ++ } catch (UnknownHostException e) { ++ return null; ++ } ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeU32(((long)addr) & 0xFFFFFFFFL); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Address.java b/external/asmack/build/src/trunk/org/xbill/DNS/Address.java +new file mode 100644 +index 0000000..2316e36 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Address.java +@@ -0,0 +1,402 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.net.*; ++import java.net.Inet6Address; ++ ++/** ++ * Routines dealing with IP addresses. Includes functions similar to ++ * those in the java.net.InetAddress class. ++ * ++ * @author Brian Wellington ++ */ ++ ++public final class Address { ++ ++public static final int IPv4 = 1; ++public static final int IPv6 = 2; ++ ++private ++Address() {} ++ ++private static byte [] ++parseV4(String s) { ++ int numDigits; ++ int currentOctet; ++ byte [] values = new byte[4]; ++ int currentValue; ++ int length = s.length(); ++ ++ currentOctet = 0; ++ currentValue = 0; ++ numDigits = 0; ++ for (int i = 0; i < length; i++) { ++ char c = s.charAt(i); ++ if (c >= '0' && c <= '9') { ++ /* Can't have more than 3 digits per octet. */ ++ if (numDigits == 3) ++ return null; ++ /* Octets shouldn't start with 0, unless they are 0. */ ++ if (numDigits > 0 && currentValue == 0) ++ return null; ++ numDigits++; ++ currentValue *= 10; ++ currentValue += (c - '0'); ++ /* 255 is the maximum value for an octet. */ ++ if (currentValue > 255) ++ return null; ++ } else if (c == '.') { ++ /* Can't have more than 3 dots. */ ++ if (currentOctet == 3) ++ return null; ++ /* Two consecutive dots are bad. */ ++ if (numDigits == 0) ++ return null; ++ values[currentOctet++] = (byte) currentValue; ++ currentValue = 0; ++ numDigits = 0; ++ } else ++ return null; ++ } ++ /* Must have 4 octets. */ ++ if (currentOctet != 3) ++ return null; ++ /* The fourth octet can't be empty. */ ++ if (numDigits == 0) ++ return null; ++ values[currentOctet] = (byte) currentValue; ++ return values; ++} ++ ++private static byte [] ++parseV6(String s) { ++ int range = -1; ++ byte [] data = new byte[16]; ++ ++ String [] tokens = s.split(":", -1); ++ ++ int first = 0; ++ int last = tokens.length - 1; ++ ++ if (tokens[0].length() == 0) { ++ // If the first two tokens are empty, it means the string ++ // started with ::, which is fine. If only the first is ++ // empty, the string started with :, which is bad. ++ if (last - first > 0 && tokens[1].length() == 0) ++ first++; ++ else ++ return null; ++ } ++ ++ if (tokens[last].length() == 0) { ++ // If the last two tokens are empty, it means the string ++ // ended with ::, which is fine. If only the last is ++ // empty, the string ended with :, which is bad. ++ if (last - first > 0 && tokens[last - 1].length() == 0) ++ last--; ++ else ++ return null; ++ } ++ ++ if (last - first + 1 > 8) ++ return null; ++ ++ int i, j; ++ for (i = first, j = 0; i <= last; i++) { ++ if (tokens[i].length() == 0) { ++ if (range >= 0) ++ return null; ++ range = j; ++ continue; ++ } ++ ++ if (tokens[i].indexOf('.') >= 0) { ++ // An IPv4 address must be the last component ++ if (i < last) ++ return null; ++ // There can't have been more than 6 components. ++ if (i > 6) ++ return null; ++ byte [] v4addr = Address.toByteArray(tokens[i], IPv4); ++ if (v4addr == null) ++ return null; ++ for (int k = 0; k < 4; k++) ++ data[j++] = v4addr[k]; ++ break; ++ } ++ ++ try { ++ for (int k = 0; k < tokens[i].length(); k++) { ++ char c = tokens[i].charAt(k); ++ if (Character.digit(c, 16) < 0) ++ return null; ++ } ++ int x = Integer.parseInt(tokens[i], 16); ++ if (x > 0xFFFF || x < 0) ++ return null; ++ data[j++] = (byte)(x >>> 8); ++ data[j++] = (byte)(x & 0xFF); ++ } ++ catch (NumberFormatException e) { ++ return null; ++ } ++ } ++ ++ if (j < 16 && range < 0) ++ return null; ++ ++ if (range >= 0) { ++ int empty = 16 - j; ++ System.arraycopy(data, range, data, range + empty, j - range); ++ for (i = range; i < range + empty; i++) ++ data[i] = 0; ++ } ++ ++ return data; ++} ++ ++/** ++ * Convert a string containing an IP address to an array of 4 or 16 integers. ++ * @param s The address, in text format. ++ * @param family The address family. ++ * @return The address ++ */ ++public static int [] ++toArray(String s, int family) { ++ byte [] byteArray = toByteArray(s, family); ++ if (byteArray == null) ++ return null; ++ int [] intArray = new int[byteArray.length]; ++ for (int i = 0; i < byteArray.length; i++) ++ intArray[i] = byteArray[i] & 0xFF; ++ return intArray; ++} ++ ++/** ++ * Convert a string containing an IPv4 address to an array of 4 integers. ++ * @param s The address, in text format. ++ * @return The address ++ */ ++public static int [] ++toArray(String s) { ++ return toArray(s, IPv4); ++} ++ ++/** ++ * Convert a string containing an IP address to an array of 4 or 16 bytes. ++ * @param s The address, in text format. ++ * @param family The address family. ++ * @return The address ++ */ ++public static byte [] ++toByteArray(String s, int family) { ++ if (family == IPv4) ++ return parseV4(s); ++ else if (family == IPv6) ++ return parseV6(s); ++ else ++ throw new IllegalArgumentException("unknown address family"); ++} ++ ++/** ++ * Determines if a string contains a valid IP address. ++ * @param s The string ++ * @return Whether the string contains a valid IP address ++ */ ++public static boolean ++isDottedQuad(String s) { ++ byte [] address = Address.toByteArray(s, IPv4); ++ return (address != null); ++} ++ ++/** ++ * Converts a byte array containing an IPv4 address into a dotted quad string. ++ * @param addr The array ++ * @return The string representation ++ */ ++public static String ++toDottedQuad(byte [] addr) { ++ return ((addr[0] & 0xFF) + "." + (addr[1] & 0xFF) + "." + ++ (addr[2] & 0xFF) + "." + (addr[3] & 0xFF)); ++} ++ ++/** ++ * Converts an int array containing an IPv4 address into a dotted quad string. ++ * @param addr The array ++ * @return The string representation ++ */ ++public static String ++toDottedQuad(int [] addr) { ++ return (addr[0] + "." + addr[1] + "." + addr[2] + "." + addr[3]); ++} ++ ++private static Record [] ++lookupHostName(String name) throws UnknownHostException { ++ try { ++ Record [] records = new Lookup(name).run(); ++ if (records == null) ++ throw new UnknownHostException("unknown host"); ++ return records; ++ } ++ catch (TextParseException e) { ++ throw new UnknownHostException("invalid name"); ++ } ++} ++ ++private static InetAddress ++addrFromRecord(String name, Record r) throws UnknownHostException { ++ ARecord a = (ARecord) r; ++ return InetAddress.getByAddress(name, a.getAddress().getAddress()); ++} ++ ++/** ++ * Determines the IP address of a host ++ * @param name The hostname to look up ++ * @return The first matching IP address ++ * @exception UnknownHostException The hostname does not have any addresses ++ */ ++public static InetAddress ++getByName(String name) throws UnknownHostException { ++ try { ++ return getByAddress(name); ++ } catch (UnknownHostException e) { ++ Record [] records = lookupHostName(name); ++ return addrFromRecord(name, records[0]); ++ } ++} ++ ++/** ++ * Determines all IP address of a host ++ * @param name The hostname to look up ++ * @return All matching IP addresses ++ * @exception UnknownHostException The hostname does not have any addresses ++ */ ++public static InetAddress [] ++getAllByName(String name) throws UnknownHostException { ++ try { ++ InetAddress addr = getByAddress(name); ++ return new InetAddress[] {addr}; ++ } catch (UnknownHostException e) { ++ Record [] records = lookupHostName(name); ++ InetAddress [] addrs = new InetAddress[records.length]; ++ for (int i = 0; i < records.length; i++) ++ addrs[i] = addrFromRecord(name, records[i]); ++ return addrs; ++ } ++} ++ ++/** ++ * Converts an address from its string representation to an IP address. ++ * The address can be either IPv4 or IPv6. ++ * @param addr The address, in string form ++ * @return The IP addresses ++ * @exception UnknownHostException The address is not a valid IP address. ++ */ ++public static InetAddress ++getByAddress(String addr) throws UnknownHostException { ++ byte [] bytes; ++ bytes = toByteArray(addr, IPv4); ++ if (bytes != null) ++ return InetAddress.getByAddress(bytes); ++ bytes = toByteArray(addr, IPv6); ++ if (bytes != null) ++ return InetAddress.getByAddress(bytes); ++ throw new UnknownHostException("Invalid address: " + addr); ++} ++ ++/** ++ * Converts an address from its string representation to an IP address in ++ * a particular family. ++ * @param addr The address, in string form ++ * @param family The address family, either IPv4 or IPv6. ++ * @return The IP addresses ++ * @exception UnknownHostException The address is not a valid IP address in ++ * the specified address family. ++ */ ++public static InetAddress ++getByAddress(String addr, int family) throws UnknownHostException { ++ if (family != IPv4 && family != IPv6) ++ throw new IllegalArgumentException("unknown address family"); ++ byte [] bytes; ++ bytes = toByteArray(addr, family); ++ if (bytes != null) ++ return InetAddress.getByAddress(bytes); ++ throw new UnknownHostException("Invalid address: " + addr); ++} ++ ++/** ++ * Determines the hostname for an address ++ * @param addr The address to look up ++ * @return The associated host name ++ * @exception UnknownHostException There is no hostname for the address ++ */ ++public static String ++getHostName(InetAddress addr) throws UnknownHostException { ++ Name name = ReverseMap.fromAddress(addr); ++ Record [] records = new Lookup(name, Type.PTR).run(); ++ if (records == null) ++ throw new UnknownHostException("unknown address"); ++ PTRRecord ptr = (PTRRecord) records[0]; ++ return ptr.getTarget().toString(); ++} ++ ++/** ++ * Returns the family of an InetAddress. ++ * @param address The supplied address. ++ * @return The family, either IPv4 or IPv6. ++ */ ++public static int ++familyOf(InetAddress address) { ++ if (address instanceof Inet4Address) ++ return IPv4; ++ if (address instanceof Inet6Address) ++ return IPv6; ++ throw new IllegalArgumentException("unknown address family"); ++} ++ ++/** ++ * Returns the length of an address in a particular family. ++ * @param family The address family, either IPv4 or IPv6. ++ * @return The length of addresses in that family. ++ */ ++public static int ++addressLength(int family) { ++ if (family == IPv4) ++ return 4; ++ if (family == IPv6) ++ return 16; ++ throw new IllegalArgumentException("unknown address family"); ++} ++ ++/** ++ * Truncates an address to the specified number of bits. For example, ++ * truncating the address 10.1.2.3 to 8 bits would yield 10.0.0.0. ++ * @param address The source address ++ * @param maskLength The number of bits to truncate the address to. ++ */ ++public static InetAddress ++truncate(InetAddress address, int maskLength) ++{ ++ int family = familyOf(address); ++ int maxMaskLength = addressLength(family) * 8; ++ if (maskLength < 0 || maskLength > maxMaskLength) ++ throw new IllegalArgumentException("invalid mask length"); ++ if (maskLength == maxMaskLength) ++ return address; ++ byte [] bytes = address.getAddress(); ++ for (int i = maskLength / 8 + 1; i < bytes.length; i++) ++ bytes[i] = 0; ++ int maskBits = maskLength % 8; ++ int bitmask = 0; ++ for (int i = 0; i < maskBits; i++) ++ bitmask |= (1 << (7 - i)); ++ bytes[maskLength / 8] &= bitmask; ++ try { ++ return InetAddress.getByAddress(bytes); ++ } catch (UnknownHostException e) { ++ throw new IllegalArgumentException("invalid address"); ++ } ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/CERTRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/CERTRecord.java +new file mode 100644 +index 0000000..39bcef3 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/CERTRecord.java +@@ -0,0 +1,224 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import org.xbill.DNS.utils.*; ++ ++/** ++ * Certificate Record - Stores a certificate associated with a name. The ++ * certificate might also be associated with a KEYRecord. ++ * @see KEYRecord ++ * ++ * @author Brian Wellington ++ */ ++ ++public class CERTRecord extends Record { ++ ++public static class CertificateType { ++ /** Certificate type identifiers. See RFC 4398 for more detail. */ ++ ++ private CertificateType() {} ++ ++ /** PKIX (X.509v3) */ ++ public static final int PKIX = 1; ++ ++ /** Simple Public Key Infrastructure */ ++ public static final int SPKI = 2; ++ ++ /** Pretty Good Privacy */ ++ public static final int PGP = 3; ++ ++ /** URL of an X.509 data object */ ++ public static final int IPKIX = 4; ++ ++ /** URL of an SPKI certificate */ ++ public static final int ISPKI = 5; ++ ++ /** Fingerprint and URL of an OpenPGP packet */ ++ public static final int IPGP = 6; ++ ++ /** Attribute Certificate */ ++ public static final int ACPKIX = 7; ++ ++ /** URL of an Attribute Certificate */ ++ public static final int IACPKIX = 8; ++ ++ /** Certificate format defined by URI */ ++ public static final int URI = 253; ++ ++ /** Certificate format defined by OID */ ++ public static final int OID = 254; ++ ++ private static Mnemonic types = new Mnemonic("Certificate type", ++ Mnemonic.CASE_UPPER); ++ ++ static { ++ types.setMaximum(0xFFFF); ++ types.setNumericAllowed(true); ++ ++ types.add(PKIX, "PKIX"); ++ types.add(SPKI, "SPKI"); ++ types.add(PGP, "PGP"); ++ types.add(PKIX, "IPKIX"); ++ types.add(SPKI, "ISPKI"); ++ types.add(PGP, "IPGP"); ++ types.add(PGP, "ACPKIX"); ++ types.add(PGP, "IACPKIX"); ++ types.add(URI, "URI"); ++ types.add(OID, "OID"); ++ } ++ ++ /** ++ * Converts a certificate type into its textual representation ++ */ ++ public static String ++ string(int type) { ++ return types.getText(type); ++ } ++ ++ /** ++ * Converts a textual representation of an certificate type into its ++ * numeric code. Integers in the range 0..65535 are also accepted. ++ * @param s The textual representation of the algorithm ++ * @return The algorithm code, or -1 on error. ++ */ ++ public static int ++ value(String s) { ++ return types.getValue(s); ++ } ++} ++ ++/** PKIX (X.509v3) */ ++public static final int PKIX = CertificateType.PKIX; ++ ++/** Simple Public Key Infrastructure */ ++public static final int SPKI = CertificateType.SPKI; ++ ++/** Pretty Good Privacy */ ++public static final int PGP = CertificateType.PGP; ++ ++/** Certificate format defined by URI */ ++public static final int URI = CertificateType.URI; ++ ++/** Certificate format defined by IOD */ ++public static final int OID = CertificateType.OID; ++ ++private static final long serialVersionUID = 4763014646517016835L; ++ ++private int certType, keyTag; ++private int alg; ++private byte [] cert; ++ ++CERTRecord() {} ++ ++Record ++getObject() { ++ return new CERTRecord(); ++} ++ ++/** ++ * Creates a CERT Record from the given data ++ * @param certType The type of certificate (see constants) ++ * @param keyTag The ID of the associated KEYRecord, if present ++ * @param alg The algorithm of the associated KEYRecord, if present ++ * @param cert Binary data representing the certificate ++ */ ++public ++CERTRecord(Name name, int dclass, long ttl, int certType, int keyTag, ++ int alg, byte [] cert) ++{ ++ super(name, Type.CERT, dclass, ttl); ++ this.certType = checkU16("certType", certType); ++ this.keyTag = checkU16("keyTag", keyTag); ++ this.alg = checkU8("alg", alg); ++ this.cert = cert; ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ certType = in.readU16(); ++ keyTag = in.readU16(); ++ alg = in.readU8(); ++ cert = in.readByteArray(); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ String certTypeString = st.getString(); ++ certType = CertificateType.value(certTypeString); ++ if (certType < 0) ++ throw st.exception("Invalid certificate type: " + ++ certTypeString); ++ keyTag = st.getUInt16(); ++ String algString = st.getString(); ++ alg = DNSSEC.Algorithm.value(algString); ++ if (alg < 0) ++ throw st.exception("Invalid algorithm: " + algString); ++ cert = st.getBase64(); ++} ++ ++/** ++ * Converts rdata to a String ++ */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append (certType); ++ sb.append (" "); ++ sb.append (keyTag); ++ sb.append (" "); ++ sb.append (alg); ++ if (cert != null) { ++ if (Options.check("multiline")) { ++ sb.append(" (\n"); ++ sb.append(base64.formatString(cert, 64, "\t", true)); ++ } else { ++ sb.append(" "); ++ sb.append(base64.toString(cert)); ++ } ++ } ++ return sb.toString(); ++} ++ ++/** ++ * Returns the type of certificate ++ */ ++public int ++getCertType() { ++ return certType; ++} ++ ++/** ++ * Returns the ID of the associated KEYRecord, if present ++ */ ++public int ++getKeyTag() { ++ return keyTag; ++} ++ ++/** ++ * Returns the algorithm of the associated KEYRecord, if present ++ */ ++public int ++getAlgorithm() { ++ return alg; ++} ++ ++/** ++ * Returns the binary representation of the certificate ++ */ ++public byte [] ++getCert() { ++ return cert; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeU16(certType); ++ out.writeU16(keyTag); ++ out.writeU8(alg); ++ out.writeByteArray(cert); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/CNAMERecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/CNAMERecord.java +new file mode 100644 +index 0000000..8db9453 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/CNAMERecord.java +@@ -0,0 +1,45 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * CNAME Record - maps an alias to its real name ++ * ++ * @author Brian Wellington ++ */ ++ ++public class CNAMERecord extends SingleCompressedNameBase { ++ ++private static final long serialVersionUID = -4020373886892538580L; ++ ++CNAMERecord() {} ++ ++Record ++getObject() { ++ return new CNAMERecord(); ++} ++ ++/** ++ * Creates a new CNAMERecord with the given data ++ * @param alias The name to which the CNAME alias points ++ */ ++public ++CNAMERecord(Name name, int dclass, long ttl, Name alias) { ++ super(name, Type.CNAME, dclass, ttl, alias, "alias"); ++} ++ ++/** ++ * Gets the target of the CNAME Record ++ */ ++public Name ++getTarget() { ++ return getSingleName(); ++} ++ ++/** Gets the alias specified by the CNAME Record */ ++public Name ++getAlias() { ++ return getSingleName(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Cache.java b/external/asmack/build/src/trunk/org/xbill/DNS/Cache.java +new file mode 100644 +index 0000000..5497f45 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Cache.java +@@ -0,0 +1,846 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.util.*; ++ ++/** ++ * A cache of DNS records. The cache obeys TTLs, so items are purged after ++ * their validity period is complete. Negative answers are cached, to ++ * avoid repeated failed DNS queries. The credibility of each RRset is ++ * maintained, so that more credible records replace less credible records, ++ * and lookups can specify the minimum credibility of data they are requesting. ++ * @see RRset ++ * @see Credibility ++ * ++ * @author Brian Wellington ++ */ ++ ++public class Cache { ++ ++private interface Element { ++ public boolean expired(); ++ public int compareCredibility(int cred); ++ public int getType(); ++} ++ ++private static int ++limitExpire(long ttl, long maxttl) { ++ if (maxttl >= 0 && maxttl < ttl) ++ ttl = maxttl; ++ long expire = (System.currentTimeMillis() / 1000) + ttl; ++ if (expire < 0 || expire > Integer.MAX_VALUE) ++ return Integer.MAX_VALUE; ++ return (int)expire; ++} ++ ++private static class CacheRRset extends RRset implements Element { ++ private static final long serialVersionUID = 5971755205903597024L; ++ ++ int credibility; ++ int expire; ++ ++ public ++ CacheRRset(Record rec, int cred, long maxttl) { ++ super(); ++ this.credibility = cred; ++ this.expire = limitExpire(rec.getTTL(), maxttl); ++ addRR(rec); ++ } ++ ++ public ++ CacheRRset(RRset rrset, int cred, long maxttl) { ++ super(rrset); ++ this.credibility = cred; ++ this.expire = limitExpire(rrset.getTTL(), maxttl); ++ } ++ ++ public final boolean ++ expired() { ++ int now = (int)(System.currentTimeMillis() / 1000); ++ return (now >= expire); ++ } ++ ++ public final int ++ compareCredibility(int cred) { ++ return credibility - cred; ++ } ++ ++ public String ++ toString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(super.toString()); ++ sb.append(" cl = "); ++ sb.append(credibility); ++ return sb.toString(); ++ } ++} ++ ++private static class NegativeElement implements Element { ++ int type; ++ Name name; ++ int credibility; ++ int expire; ++ ++ public ++ NegativeElement(Name name, int type, SOARecord soa, int cred, ++ long maxttl) ++ { ++ this.name = name; ++ this.type = type; ++ long cttl = 0; ++ if (soa != null) ++ cttl = soa.getMinimum(); ++ this.credibility = cred; ++ this.expire = limitExpire(cttl, maxttl); ++ } ++ ++ public int ++ getType() { ++ return type; ++ } ++ ++ public final boolean ++ expired() { ++ int now = (int)(System.currentTimeMillis() / 1000); ++ return (now >= expire); ++ } ++ ++ public final int ++ compareCredibility(int cred) { ++ return credibility - cred; ++ } ++ ++ public String ++ toString() { ++ StringBuffer sb = new StringBuffer(); ++ if (type == 0) ++ sb.append("NXDOMAIN " + name); ++ else ++ sb.append("NXRRSET " + name + " " + Type.string(type)); ++ sb.append(" cl = "); ++ sb.append(credibility); ++ return sb.toString(); ++ } ++} ++ ++private static class CacheMap extends LinkedHashMap { ++ private int maxsize = -1; ++ ++ CacheMap(int maxsize) { ++ super(16, (float) 0.75, true); ++ this.maxsize = maxsize; ++ } ++ ++ int ++ getMaxSize() { ++ return maxsize; ++ } ++ ++ void ++ setMaxSize(int maxsize) { ++ /* ++ * Note that this doesn't shrink the size of the map if ++ * the maximum size is lowered, but it should shrink as ++ * entries expire. ++ */ ++ this.maxsize = maxsize; ++ } ++ ++ protected boolean removeEldestEntry(Map.Entry eldest) { ++ return maxsize >= 0 && size() > maxsize; ++ } ++} ++ ++private CacheMap data; ++private int maxncache = -1; ++private int maxcache = -1; ++private int dclass; ++ ++private static final int defaultMaxEntries = 50000; ++ ++/** ++ * Creates an empty Cache ++ * ++ * @param dclass The DNS class of this cache ++ * @see DClass ++ */ ++public ++Cache(int dclass) { ++ this.dclass = dclass; ++ data = new CacheMap(defaultMaxEntries); ++} ++ ++/** ++ * Creates an empty Cache for class IN. ++ * @see DClass ++ */ ++public ++Cache() { ++ this(DClass.IN); ++} ++ ++/** ++ * Creates a Cache which initially contains all records in the specified file. ++ */ ++public ++Cache(String file) throws IOException { ++ data = new CacheMap(defaultMaxEntries); ++ Master m = new Master(file); ++ Record record; ++ while ((record = m.nextRecord()) != null) ++ addRecord(record, Credibility.HINT, m); ++} ++ ++private synchronized Object ++exactName(Name name) { ++ return data.get(name); ++} ++ ++private synchronized void ++removeName(Name name) { ++ data.remove(name); ++} ++ ++private synchronized Element [] ++allElements(Object types) { ++ if (types instanceof List) { ++ List typelist = (List) types; ++ int size = typelist.size(); ++ return (Element []) typelist.toArray(new Element[size]); ++ } else { ++ Element set = (Element) types; ++ return new Element[] {set}; ++ } ++} ++ ++private synchronized Element ++oneElement(Name name, Object types, int type, int minCred) { ++ Element found = null; ++ ++ if (type == Type.ANY) ++ throw new IllegalArgumentException("oneElement(ANY)"); ++ if (types instanceof List) { ++ List list = (List) types; ++ for (int i = 0; i < list.size(); i++) { ++ Element set = (Element) list.get(i); ++ if (set.getType() == type) { ++ found = set; ++ break; ++ } ++ } ++ } else { ++ Element set = (Element) types; ++ if (set.getType() == type) ++ found = set; ++ } ++ if (found == null) ++ return null; ++ if (found.expired()) { ++ removeElement(name, type); ++ return null; ++ } ++ if (found.compareCredibility(minCred) < 0) ++ return null; ++ return found; ++} ++ ++private synchronized Element ++findElement(Name name, int type, int minCred) { ++ Object types = exactName(name); ++ if (types == null) ++ return null; ++ return oneElement(name, types, type, minCred); ++} ++ ++private synchronized void ++addElement(Name name, Element element) { ++ Object types = data.get(name); ++ if (types == null) { ++ data.put(name, element); ++ return; ++ } ++ int type = element.getType(); ++ if (types instanceof List) { ++ List list = (List) types; ++ for (int i = 0; i < list.size(); i++) { ++ Element elt = (Element) list.get(i); ++ if (elt.getType() == type) { ++ list.set(i, element); ++ return; ++ } ++ } ++ list.add(element); ++ } else { ++ Element elt = (Element) types; ++ if (elt.getType() == type) ++ data.put(name, element); ++ else { ++ LinkedList list = new LinkedList(); ++ list.add(elt); ++ list.add(element); ++ data.put(name, list); ++ } ++ } ++} ++ ++private synchronized void ++removeElement(Name name, int type) { ++ Object types = data.get(name); ++ if (types == null) { ++ return; ++ } ++ if (types instanceof List) { ++ List list = (List) types; ++ for (int i = 0; i < list.size(); i++) { ++ Element elt = (Element) list.get(i); ++ if (elt.getType() == type) { ++ list.remove(i); ++ if (list.size() == 0) ++ data.remove(name); ++ return; ++ } ++ } ++ } else { ++ Element elt = (Element) types; ++ if (elt.getType() != type) ++ return; ++ data.remove(name); ++ } ++} ++ ++/** Empties the Cache. */ ++public synchronized void ++clearCache() { ++ data.clear(); ++} ++ ++/** ++ * Adds a record to the Cache. ++ * @param r The record to be added ++ * @param cred The credibility of the record ++ * @param o The source of the record (this could be a Message, for example) ++ * @see Record ++ */ ++public synchronized void ++addRecord(Record r, int cred, Object o) { ++ Name name = r.getName(); ++ int type = r.getRRsetType(); ++ if (!Type.isRR(type)) ++ return; ++ Element element = findElement(name, type, cred); ++ if (element == null) { ++ CacheRRset crrset = new CacheRRset(r, cred, maxcache); ++ addRRset(crrset, cred); ++ } else if (element.compareCredibility(cred) == 0) { ++ if (element instanceof CacheRRset) { ++ CacheRRset crrset = (CacheRRset) element; ++ crrset.addRR(r); ++ } ++ } ++} ++ ++/** ++ * Adds an RRset to the Cache. ++ * @param rrset The RRset to be added ++ * @param cred The credibility of these records ++ * @see RRset ++ */ ++public synchronized void ++addRRset(RRset rrset, int cred) { ++ long ttl = rrset.getTTL(); ++ Name name = rrset.getName(); ++ int type = rrset.getType(); ++ Element element = findElement(name, type, 0); ++ if (ttl == 0) { ++ if (element != null && element.compareCredibility(cred) <= 0) ++ removeElement(name, type); ++ } else { ++ if (element != null && element.compareCredibility(cred) <= 0) ++ element = null; ++ if (element == null) { ++ CacheRRset crrset; ++ if (rrset instanceof CacheRRset) ++ crrset = (CacheRRset) rrset; ++ else ++ crrset = new CacheRRset(rrset, cred, maxcache); ++ addElement(name, crrset); ++ } ++ } ++} ++ ++/** ++ * Adds a negative entry to the Cache. ++ * @param name The name of the negative entry ++ * @param type The type of the negative entry ++ * @param soa The SOA record to add to the negative cache entry, or null. ++ * The negative cache ttl is derived from the SOA. ++ * @param cred The credibility of the negative entry ++ */ ++public synchronized void ++addNegative(Name name, int type, SOARecord soa, int cred) { ++ long ttl = 0; ++ if (soa != null) ++ ttl = soa.getTTL(); ++ Element element = findElement(name, type, 0); ++ if (ttl == 0) { ++ if (element != null && element.compareCredibility(cred) <= 0) ++ removeElement(name, type); ++ } else { ++ if (element != null && element.compareCredibility(cred) <= 0) ++ element = null; ++ if (element == null) ++ addElement(name, new NegativeElement(name, type, ++ soa, cred, ++ maxncache)); ++ } ++} ++ ++/** ++ * Finds all matching sets or something that causes the lookup to stop. ++ */ ++protected synchronized SetResponse ++lookup(Name name, int type, int minCred) { ++ int labels; ++ int tlabels; ++ Element element; ++ Name tname; ++ Object types; ++ SetResponse sr; ++ ++ labels = name.labels(); ++ ++ for (tlabels = labels; tlabels >= 1; tlabels--) { ++ boolean isRoot = (tlabels == 1); ++ boolean isExact = (tlabels == labels); ++ ++ if (isRoot) ++ tname = Name.root; ++ else if (isExact) ++ tname = name; ++ else ++ tname = new Name(name, labels - tlabels); ++ ++ types = data.get(tname); ++ if (types == null) ++ continue; ++ ++ /* ++ * If this is the name, look for the actual type or a CNAME ++ * (unless it's an ANY query, where we return everything). ++ * Otherwise, look for a DNAME. ++ */ ++ if (isExact && type == Type.ANY) { ++ sr = new SetResponse(SetResponse.SUCCESSFUL); ++ Element [] elements = allElements(types); ++ int added = 0; ++ for (int i = 0; i < elements.length; i++) { ++ element = elements[i]; ++ if (element.expired()) { ++ removeElement(tname, element.getType()); ++ continue; ++ } ++ if (!(element instanceof CacheRRset)) ++ continue; ++ if (element.compareCredibility(minCred) < 0) ++ continue; ++ sr.addRRset((CacheRRset)element); ++ added++; ++ } ++ /* There were positive entries */ ++ if (added > 0) ++ return sr; ++ } else if (isExact) { ++ element = oneElement(tname, types, type, minCred); ++ if (element != null && ++ element instanceof CacheRRset) ++ { ++ sr = new SetResponse(SetResponse.SUCCESSFUL); ++ sr.addRRset((CacheRRset) element); ++ return sr; ++ } else if (element != null) { ++ sr = new SetResponse(SetResponse.NXRRSET); ++ return sr; ++ } ++ ++ element = oneElement(tname, types, Type.CNAME, minCred); ++ if (element != null && ++ element instanceof CacheRRset) ++ { ++ return new SetResponse(SetResponse.CNAME, ++ (CacheRRset) element); ++ } ++ } else { ++ element = oneElement(tname, types, Type.DNAME, minCred); ++ if (element != null && ++ element instanceof CacheRRset) ++ { ++ return new SetResponse(SetResponse.DNAME, ++ (CacheRRset) element); ++ } ++ } ++ ++ /* Look for an NS */ ++ element = oneElement(tname, types, Type.NS, minCred); ++ if (element != null && element instanceof CacheRRset) ++ return new SetResponse(SetResponse.DELEGATION, ++ (CacheRRset) element); ++ ++ /* Check for the special NXDOMAIN element. */ ++ if (isExact) { ++ element = oneElement(tname, types, 0, minCred); ++ if (element != null) ++ return SetResponse.ofType(SetResponse.NXDOMAIN); ++ } ++ ++ } ++ return SetResponse.ofType(SetResponse.UNKNOWN); ++} ++ ++/** ++ * Looks up Records in the Cache. This follows CNAMEs and handles negatively ++ * cached data. ++ * @param name The name to look up ++ * @param type The type to look up ++ * @param minCred The minimum acceptable credibility ++ * @return A SetResponse object ++ * @see SetResponse ++ * @see Credibility ++ */ ++public SetResponse ++lookupRecords(Name name, int type, int minCred) { ++ return lookup(name, type, minCred); ++} ++ ++private RRset [] ++findRecords(Name name, int type, int minCred) { ++ SetResponse cr = lookupRecords(name, type, minCred); ++ if (cr.isSuccessful()) ++ return cr.answers(); ++ else ++ return null; ++} ++ ++/** ++ * Looks up credible Records in the Cache (a wrapper around lookupRecords). ++ * Unlike lookupRecords, this given no indication of why failure occurred. ++ * @param name The name to look up ++ * @param type The type to look up ++ * @return An array of RRsets, or null ++ * @see Credibility ++ */ ++public RRset [] ++findRecords(Name name, int type) { ++ return findRecords(name, type, Credibility.NORMAL); ++} ++ ++/** ++ * Looks up Records in the Cache (a wrapper around lookupRecords). Unlike ++ * lookupRecords, this given no indication of why failure occurred. ++ * @param name The name to look up ++ * @param type The type to look up ++ * @return An array of RRsets, or null ++ * @see Credibility ++ */ ++public RRset [] ++findAnyRecords(Name name, int type) { ++ return findRecords(name, type, Credibility.GLUE); ++} ++ ++private final int ++getCred(int section, boolean isAuth) { ++ if (section == Section.ANSWER) { ++ if (isAuth) ++ return Credibility.AUTH_ANSWER; ++ else ++ return Credibility.NONAUTH_ANSWER; ++ } else if (section == Section.AUTHORITY) { ++ if (isAuth) ++ return Credibility.AUTH_AUTHORITY; ++ else ++ return Credibility.NONAUTH_AUTHORITY; ++ } else if (section == Section.ADDITIONAL) { ++ return Credibility.ADDITIONAL; ++ } else ++ throw new IllegalArgumentException("getCred: invalid section"); ++} ++ ++private static void ++markAdditional(RRset rrset, Set names) { ++ Record first = rrset.first(); ++ if (first.getAdditionalName() == null) ++ return; ++ ++ Iterator it = rrset.rrs(); ++ while (it.hasNext()) { ++ Record r = (Record) it.next(); ++ Name name = r.getAdditionalName(); ++ if (name != null) ++ names.add(name); ++ } ++} ++ ++/** ++ * Adds all data from a Message into the Cache. Each record is added with ++ * the appropriate credibility, and negative answers are cached as such. ++ * @param in The Message to be added ++ * @return A SetResponse that reflects what would be returned from a cache ++ * lookup, or null if nothing useful could be cached from the message. ++ * @see Message ++ */ ++public SetResponse ++addMessage(Message in) { ++ boolean isAuth = in.getHeader().getFlag(Flags.AA); ++ Record question = in.getQuestion(); ++ Name qname; ++ Name curname; ++ int qtype; ++ int qclass; ++ int cred; ++ int rcode = in.getHeader().getRcode(); ++ boolean completed = false; ++ RRset [] answers, auth, addl; ++ SetResponse response = null; ++ boolean verbose = Options.check("verbosecache"); ++ HashSet additionalNames; ++ ++ if ((rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN) || ++ question == null) ++ return null; ++ ++ qname = question.getName(); ++ qtype = question.getType(); ++ qclass = question.getDClass(); ++ ++ curname = qname; ++ ++ additionalNames = new HashSet(); ++ ++ answers = in.getSectionRRsets(Section.ANSWER); ++ for (int i = 0; i < answers.length; i++) { ++ if (answers[i].getDClass() != qclass) ++ continue; ++ int type = answers[i].getType(); ++ Name name = answers[i].getName(); ++ cred = getCred(Section.ANSWER, isAuth); ++ if ((type == qtype || qtype == Type.ANY) && ++ name.equals(curname)) ++ { ++ addRRset(answers[i], cred); ++ completed = true; ++ if (curname == qname) { ++ if (response == null) ++ response = new SetResponse( ++ SetResponse.SUCCESSFUL); ++ response.addRRset(answers[i]); ++ } ++ markAdditional(answers[i], additionalNames); ++ } else if (type == Type.CNAME && name.equals(curname)) { ++ CNAMERecord cname; ++ addRRset(answers[i], cred); ++ if (curname == qname) ++ response = new SetResponse(SetResponse.CNAME, ++ answers[i]); ++ cname = (CNAMERecord) answers[i].first(); ++ curname = cname.getTarget(); ++ } else if (type == Type.DNAME && curname.subdomain(name)) { ++ DNAMERecord dname; ++ addRRset(answers[i], cred); ++ if (curname == qname) ++ response = new SetResponse(SetResponse.DNAME, ++ answers[i]); ++ dname = (DNAMERecord) answers[i].first(); ++ try { ++ curname = curname.fromDNAME(dname); ++ } ++ catch (NameTooLongException e) { ++ break; ++ } ++ } ++ } ++ ++ auth = in.getSectionRRsets(Section.AUTHORITY); ++ RRset soa = null, ns = null; ++ for (int i = 0; i < auth.length; i++) { ++ if (auth[i].getType() == Type.SOA && ++ curname.subdomain(auth[i].getName())) ++ soa = auth[i]; ++ else if (auth[i].getType() == Type.NS && ++ curname.subdomain(auth[i].getName())) ++ ns = auth[i]; ++ } ++ if (!completed) { ++ /* This is a negative response or a referral. */ ++ int cachetype = (rcode == Rcode.NXDOMAIN) ? 0 : qtype; ++ if (rcode == Rcode.NXDOMAIN || soa != null || ns == null) { ++ /* Negative response */ ++ cred = getCred(Section.AUTHORITY, isAuth); ++ SOARecord soarec = null; ++ if (soa != null) ++ soarec = (SOARecord) soa.first(); ++ addNegative(curname, cachetype, soarec, cred); ++ if (response == null) { ++ int responseType; ++ if (rcode == Rcode.NXDOMAIN) ++ responseType = SetResponse.NXDOMAIN; ++ else ++ responseType = SetResponse.NXRRSET; ++ response = SetResponse.ofType(responseType); ++ } ++ /* DNSSEC records are not cached. */ ++ } else { ++ /* Referral response */ ++ cred = getCred(Section.AUTHORITY, isAuth); ++ addRRset(ns, cred); ++ markAdditional(ns, additionalNames); ++ if (response == null) ++ response = new SetResponse( ++ SetResponse.DELEGATION, ++ ns); ++ } ++ } else if (rcode == Rcode.NOERROR && ns != null) { ++ /* Cache the NS set from a positive response. */ ++ cred = getCred(Section.AUTHORITY, isAuth); ++ addRRset(ns, cred); ++ markAdditional(ns, additionalNames); ++ } ++ ++ addl = in.getSectionRRsets(Section.ADDITIONAL); ++ for (int i = 0; i < addl.length; i++) { ++ int type = addl[i].getType(); ++ if (type != Type.A && type != Type.AAAA && type != Type.A6) ++ continue; ++ Name name = addl[i].getName(); ++ if (!additionalNames.contains(name)) ++ continue; ++ cred = getCred(Section.ADDITIONAL, isAuth); ++ addRRset(addl[i], cred); ++ } ++ if (verbose) ++ System.out.println("addMessage: " + response); ++ return (response); ++} ++ ++/** ++ * Flushes an RRset from the cache ++ * @param name The name of the records to be flushed ++ * @param type The type of the records to be flushed ++ * @see RRset ++ */ ++public void ++flushSet(Name name, int type) { ++ removeElement(name, type); ++} ++ ++/** ++ * Flushes all RRsets with a given name from the cache ++ * @param name The name of the records to be flushed ++ * @see RRset ++ */ ++public void ++flushName(Name name) { ++ removeName(name); ++} ++ ++/** ++ * Sets the maximum length of time that a negative response will be stored ++ * in this Cache. A negative value disables this feature (that is, sets ++ * no limit). ++ */ ++public void ++setMaxNCache(int seconds) { ++ maxncache = seconds; ++} ++ ++/** ++ * Gets the maximum length of time that a negative response will be stored ++ * in this Cache. A negative value indicates no limit. ++ */ ++public int ++getMaxNCache() { ++ return maxncache; ++} ++ ++/** ++ * Sets the maximum length of time that records will be stored in this ++ * Cache. A negative value disables this feature (that is, sets no limit). ++ */ ++public void ++setMaxCache(int seconds) { ++ maxcache = seconds; ++} ++ ++/** ++ * Gets the maximum length of time that records will be stored ++ * in this Cache. A negative value indicates no limit. ++ */ ++public int ++getMaxCache() { ++ return maxcache; ++} ++ ++/** ++ * Gets the current number of entries in the Cache, where an entry consists ++ * of all records with a specific Name. ++ */ ++public int ++getSize() { ++ return data.size(); ++} ++ ++/** ++ * Gets the maximum number of entries in the Cache, where an entry consists ++ * of all records with a specific Name. A negative value is treated as an ++ * infinite limit. ++ */ ++public int ++getMaxEntries() { ++ return data.getMaxSize(); ++} ++ ++/** ++ * Sets the maximum number of entries in the Cache, where an entry consists ++ * of all records with a specific Name. A negative value is treated as an ++ * infinite limit. ++ * ++ * Note that setting this to a value lower than the current number ++ * of entries will not cause the Cache to shrink immediately. ++ * ++ * The default maximum number of entries is 50000. ++ * ++ * @param entries The maximum number of entries in the Cache. ++ */ ++public void ++setMaxEntries(int entries) { ++ data.setMaxSize(entries); ++} ++ ++/** ++ * Returns the DNS class of this cache. ++ */ ++public int ++getDClass() { ++ return dclass; ++} ++ ++/** ++ * Returns the contents of the Cache as a string. ++ */ ++public String ++toString() { ++ StringBuffer sb = new StringBuffer(); ++ synchronized (this) { ++ Iterator it = data.values().iterator(); ++ while (it.hasNext()) { ++ Element [] elements = allElements(it.next()); ++ for (int i = 0; i < elements.length; i++) { ++ sb.append(elements[i]); ++ sb.append("\n"); ++ } ++ } ++ } ++ return sb.toString(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Client.java b/external/asmack/build/src/trunk/org/xbill/DNS/Client.java +new file mode 100644 +index 0000000..2eef44f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Client.java +@@ -0,0 +1,58 @@ ++// Copyright (c) 2005 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.net.*; ++import java.nio.channels.*; ++import org.xbill.DNS.utils.hexdump; ++ ++class Client { ++ ++protected long endTime; ++protected SelectionKey key; ++ ++protected ++Client(SelectableChannel channel, long endTime) throws IOException { ++ boolean done = false; ++ Selector selector = null; ++ this.endTime = endTime; ++ try { ++ selector = Selector.open(); ++ channel.configureBlocking(false); ++ key = channel.register(selector, SelectionKey.OP_READ); ++ done = true; ++ } ++ finally { ++ if (!done && selector != null) ++ selector.close(); ++ if (!done) ++ channel.close(); ++ } ++} ++ ++static protected void ++blockUntil(SelectionKey key, long endTime) throws IOException { ++ long timeout = endTime - System.currentTimeMillis(); ++ int nkeys = 0; ++ if (timeout > 0) ++ nkeys = key.selector().select(timeout); ++ else if (timeout == 0) ++ nkeys = key.selector().selectNow(); ++ if (nkeys == 0) ++ throw new SocketTimeoutException(); ++} ++ ++static protected void ++verboseLog(String prefix, byte [] data) { ++ if (Options.check("verbosemsg")) ++ System.err.println(hexdump.dump(prefix, data)); ++} ++ ++void ++cleanup() throws IOException { ++ key.selector().close(); ++ key.channel().close(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ClientSubnetOption.java b/external/asmack/build/src/trunk/org/xbill/DNS/ClientSubnetOption.java +new file mode 100644 +index 0000000..4a98a12 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/ClientSubnetOption.java +@@ -0,0 +1,175 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.net.*; ++import java.util.regex.*; ++ ++/** ++ * The Client Subnet EDNS Option, defined in ++ * http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-00 ++ * ("Client subnet in DNS requests"). ++ * ++ * The option is used to convey information about the IP address of the ++ * originating client, so that an authoritative server can make decisions ++ * based on this address, rather than the address of the intermediate ++ * caching name server. ++ * ++ * The option is transmitted as part of an OPTRecord in the additional section ++ * of a DNS message, as defined by RFC 2671 (EDNS0). ++ * ++ * An option code has not been assigned by IANA; the value 20730 (used here) is ++ * also used by several other implementations. ++ * ++ * The wire format of the option contains a 2-byte length field (1 for IPv4, 2 ++ * for IPv6), a 1-byte source netmask, a 1-byte scope netmask, and an address ++ * truncated to the source netmask length (where the final octet is padded with ++ * bits set to 0) ++ * ++ * ++ * @see OPTRecord ++ * ++ * @author Brian Wellington ++ * @author Ming Zhou <mizhou@bnivideo.com>, Beaumaris Networks ++ */ ++public class ClientSubnetOption extends EDNSOption { ++ ++private static final long serialVersionUID = -3868158449890266347L; ++ ++private int family; ++private int sourceNetmask; ++private int scopeNetmask; ++private InetAddress address; ++ ++ClientSubnetOption() { ++ super(EDNSOption.Code.CLIENT_SUBNET); ++} ++ ++private static int ++checkMaskLength(String field, int family, int val) { ++ int max = Address.addressLength(family) * 8; ++ if (val < 0 || val > max) ++ throw new IllegalArgumentException("\"" + field + "\" " + val + ++ " must be in the range " + ++ "[0.." + max + "]"); ++ return val; ++} ++ ++/** ++ * Construct a Client Subnet option. Note that the number of significant bits in ++ * the address must not be greater than the supplied source netmask. ++ * XXX something about Java's mapped addresses ++ * @param sourceNetmask The length of the netmask pertaining to the query. ++ * In replies, it mirrors the same value as in the requests. ++ * @param scopeNetmask The length of the netmask pertaining to the reply. ++ * In requests, it MUST be set to 0. In responses, this may or may not match ++ * the source netmask. ++ * @param address The address of the client. ++ */ ++public ++ClientSubnetOption(int sourceNetmask, int scopeNetmask, InetAddress address) { ++ super(EDNSOption.Code.CLIENT_SUBNET); ++ ++ this.family = Address.familyOf(address); ++ this.sourceNetmask = checkMaskLength("source netmask", this.family, ++ sourceNetmask); ++ this.scopeNetmask = checkMaskLength("scope netmask", this.family, ++ scopeNetmask); ++ this.address = Address.truncate(address, sourceNetmask); ++ ++ if (!address.equals(this.address)) ++ throw new IllegalArgumentException("source netmask is not " + ++ "valid for address"); ++} ++ ++/** ++ * Construct a Client Subnet option with scope netmask set to 0. ++ * @param sourceNetmask The length of the netmask pertaining to the query. ++ * In replies, it mirrors the same value as in the requests. ++ * @param address The address of the client. ++ * @see ClientSubnetOption ++ */ ++public ++ClientSubnetOption(int sourceNetmask, InetAddress address) { ++ this(sourceNetmask, 0, address); ++} ++ ++/** ++ * Returns the family of the network address. This will be either IPv4 (1) ++ * or IPv6 (2). ++ */ ++public int ++getFamily() { ++ return family; ++} ++ ++/** Returns the source netmask. */ ++public int ++getSourceNetmask() { ++ return sourceNetmask; ++} ++ ++/** Returns the scope netmask. */ ++public int ++getScopeNetmask() { ++ return scopeNetmask; ++} ++ ++/** Returns the IP address of the client. */ ++public InetAddress ++getAddress() { ++ return address; ++} ++ ++void ++optionFromWire(DNSInput in) throws WireParseException { ++ family = in.readU16(); ++ if (family != Address.IPv4 && family != Address.IPv6) ++ throw new WireParseException("unknown address family"); ++ sourceNetmask = in.readU8(); ++ if (sourceNetmask > Address.addressLength(family) * 8) ++ throw new WireParseException("invalid source netmask"); ++ scopeNetmask = in.readU8(); ++ if (scopeNetmask > Address.addressLength(family) * 8) ++ throw new WireParseException("invalid scope netmask"); ++ ++ // Read the truncated address ++ byte [] addr = in.readByteArray(); ++ if (addr.length != (sourceNetmask + 7) / 8) ++ throw new WireParseException("invalid address"); ++ ++ // Convert it to a full length address. ++ byte [] fulladdr = new byte[Address.addressLength(family)]; ++ System.arraycopy(addr, 0, fulladdr, 0, addr.length); ++ ++ try { ++ address = InetAddress.getByAddress(fulladdr); ++ } catch (UnknownHostException e) { ++ throw new WireParseException("invalid address", e); ++ } ++ ++ InetAddress tmp = Address.truncate(address, sourceNetmask); ++ if (!tmp.equals(address)) ++ throw new WireParseException("invalid padding"); ++} ++ ++void ++optionToWire(DNSOutput out) { ++ out.writeU16(family); ++ out.writeU8(sourceNetmask); ++ out.writeU8(scopeNetmask); ++ out.writeByteArray(address.getAddress(), 0, (sourceNetmask + 7) / 8); ++} ++ ++String ++optionToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(address.getHostAddress()); ++ sb.append("/"); ++ sb.append(sourceNetmask); ++ sb.append(", scope netmask "); ++ sb.append(scopeNetmask); ++ return sb.toString(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Compression.java b/external/asmack/build/src/trunk/org/xbill/DNS/Compression.java +new file mode 100644 +index 0000000..e3e81c0 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Compression.java +@@ -0,0 +1,72 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * DNS Name Compression object. ++ * @see Message ++ * @see Name ++ * ++ * @author Brian Wellington ++ */ ++ ++public class Compression { ++ ++private static class Entry { ++ Name name; ++ int pos; ++ Entry next; ++} ++ ++private static final int TABLE_SIZE = 17; ++private static final int MAX_POINTER = 0x3FFF; ++private Entry [] table; ++private boolean verbose = Options.check("verbosecompression"); ++ ++/** ++ * Creates a new Compression object. ++ */ ++public ++Compression() { ++ table = new Entry[TABLE_SIZE]; ++} ++ ++/** ++ * Adds a compression entry mapping a name to a position in a message. ++ * @param pos The position at which the name is added. ++ * @param name The name being added to the message. ++ */ ++public void ++add(int pos, Name name) { ++ if (pos > MAX_POINTER) ++ return; ++ int row = (name.hashCode() & 0x7FFFFFFF) % TABLE_SIZE; ++ Entry entry = new Entry(); ++ entry.name = name; ++ entry.pos = pos; ++ entry.next = table[row]; ++ table[row] = entry; ++ if (verbose) ++ System.err.println("Adding " + name + " at " + pos); ++} ++ ++/** ++ * Retrieves the position of the given name, if it has been previously ++ * included in the message. ++ * @param name The name to find in the compression table. ++ * @return The position of the name, or -1 if not found. ++ */ ++public int ++get(Name name) { ++ int row = (name.hashCode() & 0x7FFFFFFF) % TABLE_SIZE; ++ int pos = -1; ++ for (Entry entry = table[row]; entry != null; entry = entry.next) { ++ if (entry.name.equals(name)) ++ pos = entry.pos; ++ } ++ if (verbose) ++ System.err.println("Looking for " + name + ", found " + pos); ++ return pos; ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Credibility.java b/external/asmack/build/src/trunk/org/xbill/DNS/Credibility.java +new file mode 100644 +index 0000000..fa10686 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Credibility.java +@@ -0,0 +1,50 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Constants relating to the credibility of cached data, which is based on ++ * the data's source. The constants NORMAL and ANY should be used by most ++ * callers. ++ * @see Cache ++ * @see Section ++ * ++ * @author Brian Wellington ++ */ ++ ++public final class Credibility { ++ ++private ++Credibility() {} ++ ++/** A hint or cache file on disk. */ ++public static final int HINT = 0; ++ ++/** The additional section of a response. */ ++public static final int ADDITIONAL = 1; ++ ++/** The additional section of a response. */ ++public static final int GLUE = 2; ++ ++/** The authority section of a nonauthoritative response. */ ++public static final int NONAUTH_AUTHORITY = 3; ++ ++/** The answer section of a nonauthoritative response. */ ++public static final int NONAUTH_ANSWER = 3; ++ ++/** The authority section of an authoritative response. */ ++public static final int AUTH_AUTHORITY = 4; ++ ++/** The answer section of a authoritative response. */ ++public static final int AUTH_ANSWER = 4; ++ ++/** A zone. */ ++public static final int ZONE = 5; ++ ++/** Credible data. */ ++public static final int NORMAL = 3; ++ ++/** Data not required to be credible. */ ++public static final int ANY = 1; ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/DClass.java b/external/asmack/build/src/trunk/org/xbill/DNS/DClass.java +new file mode 100644 +index 0000000..22180cf +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/DClass.java +@@ -0,0 +1,92 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Constants and functions relating to DNS classes. This is called DClass ++ * to avoid confusion with Class. ++ * ++ * @author Brian Wellington ++ */ ++ ++public final class DClass { ++ ++/** Internet */ ++public static final int IN = 1; ++ ++/** Chaos network (MIT) */ ++public static final int CH = 3; ++ ++/** Chaos network (MIT, alternate name) */ ++public static final int CHAOS = 3; ++ ++/** Hesiod name server (MIT) */ ++public static final int HS = 4; ++ ++/** Hesiod name server (MIT, alternate name) */ ++public static final int HESIOD = 4; ++ ++/** Special value used in dynamic update messages */ ++public static final int NONE = 254; ++ ++/** Matches any class */ ++public static final int ANY = 255; ++ ++private static class DClassMnemonic extends Mnemonic { ++ public ++ DClassMnemonic() { ++ super("DClass", CASE_UPPER); ++ setPrefix("CLASS"); ++ } ++ ++ public void ++ check(int val) { ++ DClass.check(val); ++ } ++} ++ ++private static Mnemonic classes = new DClassMnemonic(); ++ ++static { ++ classes.add(IN, "IN"); ++ classes.add(CH, "CH"); ++ classes.addAlias(CH, "CHAOS"); ++ classes.add(HS, "HS"); ++ classes.addAlias(HS, "HESIOD"); ++ classes.add(NONE, "NONE"); ++ classes.add(ANY, "ANY"); ++} ++ ++private ++DClass() {} ++ ++/** ++ * Checks that a numeric DClass is valid. ++ * @throws InvalidDClassException The class is out of range. ++ */ ++public static void ++check(int i) { ++ if (i < 0 || i > 0xFFFF) ++ throw new InvalidDClassException(i); ++} ++ ++/** ++ * Converts a numeric DClass into a String ++ * @return The canonical string representation of the class ++ * @throws InvalidDClassException The class is out of range. ++ */ ++public static String ++string(int i) { ++ return classes.getText(i); ++} ++ ++/** ++ * Converts a String representation of a DClass into its numeric value ++ * @return The class code, or -1 on error. ++ */ ++public static int ++value(String s) { ++ return classes.getValue(s); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/DHCIDRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/DHCIDRecord.java +new file mode 100644 +index 0000000..e160a8c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/DHCIDRecord.java +@@ -0,0 +1,65 @@ ++// Copyright (c) 2008 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import org.xbill.DNS.utils.base64; ++ ++/** ++ * DHCID - Dynamic Host Configuration Protocol (DHCP) ID (RFC 4701) ++ * ++ * @author Brian Wellington ++ */ ++ ++public class DHCIDRecord extends Record { ++ ++private static final long serialVersionUID = -8214820200808997707L; ++ ++private byte [] data; ++ ++DHCIDRecord() {} ++ ++Record ++getObject() { ++ return new DHCIDRecord(); ++} ++ ++/** ++ * Creates an DHCID Record from the given data ++ * @param data The binary data, which is opaque to DNS. ++ */ ++public ++DHCIDRecord(Name name, int dclass, long ttl, byte [] data) { ++ super(name, Type.DHCID, dclass, ttl); ++ this.data = data; ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ data = in.readByteArray(); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ data = st.getBase64(); ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeByteArray(data); ++} ++ ++String ++rrToString() { ++ return base64.toString(data); ++} ++ ++/** ++ * Returns the binary data. ++ */ ++public byte [] ++getData() { ++ return data; ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/DLVRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/DLVRecord.java +new file mode 100644 +index 0000000..8acc90f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/DLVRecord.java +@@ -0,0 +1,132 @@ ++// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import org.xbill.DNS.utils.*; ++ ++/** ++ * DLV - contains a Delegation Lookaside Validation record, which acts ++ * as the equivalent of a DS record in a lookaside zone. ++ * @see DNSSEC ++ * @see DSRecord ++ * ++ * @author David Blacka ++ * @author Brian Wellington ++ */ ++ ++public class DLVRecord extends Record { ++ ++public static final int SHA1_DIGEST_ID = DSRecord.Digest.SHA1; ++public static final int SHA256_DIGEST_ID = DSRecord.Digest.SHA1; ++ ++private static final long serialVersionUID = 1960742375677534148L; ++ ++private int footprint; ++private int alg; ++private int digestid; ++private byte [] digest; ++ ++DLVRecord() {} ++ ++Record ++getObject() { ++ return new DLVRecord(); ++} ++ ++/** ++ * Creates a DLV Record from the given data ++ * @param footprint The original KEY record's footprint (keyid). ++ * @param alg The original key algorithm. ++ * @param digestid The digest id code. ++ * @param digest A hash of the original key. ++ */ ++public ++DLVRecord(Name name, int dclass, long ttl, int footprint, int alg, ++ int digestid, byte [] digest) ++{ ++ super(name, Type.DLV, dclass, ttl); ++ this.footprint = checkU16("footprint", footprint); ++ this.alg = checkU8("alg", alg); ++ this.digestid = checkU8("digestid", digestid); ++ this.digest = digest; ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ footprint = in.readU16(); ++ alg = in.readU8(); ++ digestid = in.readU8(); ++ digest = in.readByteArray(); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ footprint = st.getUInt16(); ++ alg = st.getUInt8(); ++ digestid = st.getUInt8(); ++ digest = st.getHex(); ++} ++ ++/** ++ * Converts rdata to a String ++ */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(footprint); ++ sb.append(" "); ++ sb.append(alg); ++ sb.append(" "); ++ sb.append(digestid); ++ if (digest != null) { ++ sb.append(" "); ++ sb.append(base16.toString(digest)); ++ } ++ ++ return sb.toString(); ++} ++ ++/** ++ * Returns the key's algorithm. ++ */ ++public int ++getAlgorithm() { ++ return alg; ++} ++ ++/** ++ * Returns the key's Digest ID. ++ */ ++public int ++getDigestID() ++{ ++ return digestid; ++} ++ ++/** ++ * Returns the binary hash of the key. ++ */ ++public byte [] ++getDigest() { ++ return digest; ++} ++ ++/** ++ * Returns the key's footprint. ++ */ ++public int ++getFootprint() { ++ return footprint; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeU16(footprint); ++ out.writeU8(alg); ++ out.writeU8(digestid); ++ if (digest != null) ++ out.writeByteArray(digest); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/DNAMERecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/DNAMERecord.java +new file mode 100644 +index 0000000..cbb322f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/DNAMERecord.java +@@ -0,0 +1,45 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * DNAME Record - maps a nonterminal alias (subtree) to a different domain ++ * ++ * @author Brian Wellington ++ */ ++ ++public class DNAMERecord extends SingleNameBase { ++ ++private static final long serialVersionUID = 2670767677200844154L; ++ ++DNAMERecord() {} ++ ++Record ++getObject() { ++ return new DNAMERecord(); ++} ++ ++/** ++ * Creates a new DNAMERecord with the given data ++ * @param alias The name to which the DNAME alias points ++ */ ++public ++DNAMERecord(Name name, int dclass, long ttl, Name alias) { ++ super(name, Type.DNAME, dclass, ttl, alias, "alias"); ++} ++ ++/** ++ * Gets the target of the DNAME Record ++ */ ++public Name ++getTarget() { ++ return getSingleName(); ++} ++ ++/** Gets the alias specified by the DNAME Record */ ++public Name ++getAlias() { ++ return getSingleName(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/DNSInput.java b/external/asmack/build/src/trunk/org/xbill/DNS/DNSInput.java +new file mode 100644 +index 0000000..d3134ed +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/DNSInput.java +@@ -0,0 +1,239 @@ ++// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * An class for parsing DNS messages. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class DNSInput { ++ ++private byte [] array; ++private int pos; ++private int end; ++private int saved_pos; ++private int saved_end; ++ ++/** ++ * Creates a new DNSInput ++ * @param input The byte array to read from ++ */ ++public ++DNSInput(byte [] input) { ++ array = input; ++ pos = 0; ++ end = array.length; ++ saved_pos = -1; ++ saved_end = -1; ++} ++ ++/** ++ * Returns the current position. ++ */ ++public int ++current() { ++ return pos; ++} ++ ++/** ++ * Returns the number of bytes that can be read from this stream before ++ * reaching the end. ++ */ ++public int ++remaining() { ++ return end - pos; ++} ++ ++private void ++require(int n) throws WireParseException{ ++ if (n > remaining()) { ++ throw new WireParseException("end of input"); ++ } ++} ++ ++/** ++ * Marks the following bytes in the stream as active. ++ * @param len The number of bytes in the active region. ++ * @throws IllegalArgumentException The number of bytes in the active region ++ * is longer than the remainder of the input. ++ */ ++public void ++setActive(int len) { ++ if (len > array.length - pos) { ++ throw new IllegalArgumentException("cannot set active " + ++ "region past end of input"); ++ } ++ end = pos + len; ++} ++ ++/** ++ * Clears the active region of the string. Further operations are not ++ * restricted to part of the input. ++ */ ++public void ++clearActive() { ++ end = array.length; ++} ++ ++/** ++ * Returns the position of the end of the current active region. ++ */ ++public int ++saveActive() { ++ return end; ++} ++ ++/** ++ * Restores the previously set active region. This differs from setActive() in ++ * that restoreActive() takes an absolute position, and setActive takes an ++ * offset from the current location. ++ * @param pos The end of the active region. ++ */ ++public void ++restoreActive(int pos) { ++ if (pos > array.length) { ++ throw new IllegalArgumentException("cannot set active " + ++ "region past end of input"); ++ } ++ end = pos; ++} ++ ++/** ++ * Resets the current position of the input stream to the specified index, ++ * and clears the active region. ++ * @param index The position to continue parsing at. ++ * @throws IllegalArgumentException The index is not within the input. ++ */ ++public void ++jump(int index) { ++ if (index >= array.length) { ++ throw new IllegalArgumentException("cannot jump past " + ++ "end of input"); ++ } ++ pos = index; ++ end = array.length; ++} ++ ++/** ++ * Saves the current state of the input stream. Both the current position and ++ * the end of the active region are saved. ++ * @throws IllegalArgumentException The index is not within the input. ++ */ ++public void ++save() { ++ saved_pos = pos; ++ saved_end = end; ++} ++ ++/** ++ * Restores the input stream to its state before the call to {@link #save}. ++ */ ++public void ++restore() { ++ if (saved_pos < 0) { ++ throw new IllegalStateException("no previous state"); ++ } ++ pos = saved_pos; ++ end = saved_end; ++ saved_pos = -1; ++ saved_end = -1; ++} ++ ++/** ++ * Reads an unsigned 8 bit value from the stream, as an int. ++ * @return An unsigned 8 bit value. ++ * @throws WireParseException The end of the stream was reached. ++ */ ++public int ++readU8() throws WireParseException { ++ require(1); ++ return (array[pos++] & 0xFF); ++} ++ ++/** ++ * Reads an unsigned 16 bit value from the stream, as an int. ++ * @return An unsigned 16 bit value. ++ * @throws WireParseException The end of the stream was reached. ++ */ ++public int ++readU16() throws WireParseException { ++ require(2); ++ int b1 = array[pos++] & 0xFF; ++ int b2 = array[pos++] & 0xFF; ++ return ((b1 << 8) + b2); ++} ++ ++/** ++ * Reads an unsigned 32 bit value from the stream, as a long. ++ * @return An unsigned 32 bit value. ++ * @throws WireParseException The end of the stream was reached. ++ */ ++public long ++readU32() throws WireParseException { ++ require(4); ++ int b1 = array[pos++] & 0xFF; ++ int b2 = array[pos++] & 0xFF; ++ int b3 = array[pos++] & 0xFF; ++ int b4 = array[pos++] & 0xFF; ++ return (((long)b1 << 24) + (b2 << 16) + (b3 << 8) + b4); ++} ++ ++/** ++ * Reads a byte array of a specified length from the stream into an existing ++ * array. ++ * @param b The array to read into. ++ * @param off The offset of the array to start copying data into. ++ * @param len The number of bytes to copy. ++ * @throws WireParseException The end of the stream was reached. ++ */ ++public void ++readByteArray(byte [] b, int off, int len) throws WireParseException { ++ require(len); ++ System.arraycopy(array, pos, b, off, len); ++ pos += len; ++} ++ ++/** ++ * Reads a byte array of a specified length from the stream. ++ * @return The byte array. ++ * @throws WireParseException The end of the stream was reached. ++ */ ++public byte [] ++readByteArray(int len) throws WireParseException { ++ require(len); ++ byte [] out = new byte[len]; ++ System.arraycopy(array, pos, out, 0, len); ++ pos += len; ++ return out; ++} ++ ++/** ++ * Reads a byte array consisting of the remainder of the stream (or the ++ * active region, if one is set. ++ * @return The byte array. ++ */ ++public byte [] ++readByteArray() { ++ int len = remaining(); ++ byte [] out = new byte[len]; ++ System.arraycopy(array, pos, out, 0, len); ++ pos += len; ++ return out; ++} ++ ++/** ++ * Reads a counted string from the stream. A counted string is a one byte ++ * value indicating string length, followed by bytes of data. ++ * @return A byte array containing the string. ++ * @throws WireParseException The end of the stream was reached. ++ */ ++public byte [] ++readCountedString() throws WireParseException { ++ require(1); ++ int len = array[pos++] & 0xFF; ++ return readByteArray(len); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/DNSKEYRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/DNSKEYRecord.java +new file mode 100644 +index 0000000..6e9bafd +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/DNSKEYRecord.java +@@ -0,0 +1,91 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.security.PublicKey; ++ ++/** ++ * Key - contains a cryptographic public key for use by DNS. ++ * The data can be converted to objects implementing ++ * java.security.interfaces.PublicKey ++ * @see DNSSEC ++ * ++ * @author Brian Wellington ++ */ ++ ++public class DNSKEYRecord extends KEYBase { ++ ++public static class Protocol { ++ private Protocol() {} ++ ++ /** Key will be used for DNSSEC */ ++ public static final int DNSSEC = 3; ++} ++ ++public static class Flags { ++ private Flags() {} ++ ++ /** Key is a zone key */ ++ public static final int ZONE_KEY = 0x100; ++ ++ /** Key is a secure entry point key */ ++ public static final int SEP_KEY = 0x1; ++ ++ /** Key has been revoked */ ++ public static final int REVOKE = 0x80; ++} ++ ++private static final long serialVersionUID = -8679800040426675002L; ++ ++DNSKEYRecord() {} ++ ++Record ++getObject() { ++ return new DNSKEYRecord(); ++} ++ ++/** ++ * Creates a DNSKEY Record from the given data ++ * @param flags Flags describing the key's properties ++ * @param proto The protocol that the key was created for ++ * @param alg The key's algorithm ++ * @param key Binary representation of the key ++ */ ++public ++DNSKEYRecord(Name name, int dclass, long ttl, int flags, int proto, int alg, ++ byte [] key) ++{ ++ super(name, Type.DNSKEY, dclass, ttl, flags, proto, alg, key); ++} ++ ++/** ++ * Creates a DNSKEY Record from the given data ++ * @param flags Flags describing the key's properties ++ * @param proto The protocol that the key was created for ++ * @param alg The key's algorithm ++ * @param key The key as a PublicKey ++ * @throws DNSSEC.DNSSECException The PublicKey could not be converted into DNS ++ * format. ++ */ ++public ++DNSKEYRecord(Name name, int dclass, long ttl, int flags, int proto, int alg, ++ PublicKey key) throws DNSSEC.DNSSECException ++{ ++ super(name, Type.DNSKEY, dclass, ttl, flags, proto, alg, ++ DNSSEC.fromPublicKey(key, alg)); ++ publicKey = key; ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ flags = st.getUInt16(); ++ proto = st.getUInt8(); ++ String algString = st.getString(); ++ alg = DNSSEC.Algorithm.value(algString); ++ if (alg < 0) ++ throw st.exception("Invalid algorithm: " + algString); ++ key = st.getBase64(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/DNSOutput.java b/external/asmack/build/src/trunk/org/xbill/DNS/DNSOutput.java +new file mode 100644 +index 0000000..29a8f68 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/DNSOutput.java +@@ -0,0 +1,203 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * A class for rendering DNS messages. ++ * ++ * @author Brian Wellington ++ */ ++ ++ ++public class DNSOutput { ++ ++private byte [] array; ++private int pos; ++private int saved_pos; ++ ++/** ++ * Create a new DNSOutput with a specified size. ++ * @param size The initial size ++ */ ++public ++DNSOutput(int size) { ++ array = new byte[size]; ++ pos = 0; ++ saved_pos = -1; ++} ++ ++/** ++ * Create a new DNSOutput ++ */ ++public ++DNSOutput() { ++ this(32); ++} ++ ++/** ++ * Returns the current position. ++ */ ++public int ++current() { ++ return pos; ++} ++ ++private void ++check(long val, int bits) { ++ long max = 1; ++ max <<= bits; ++ if (val < 0 || val > max) { ++ throw new IllegalArgumentException(val + " out of range for " + ++ bits + " bit value"); ++ } ++} ++ ++private void ++need(int n) { ++ if (array.length - pos >= n) { ++ return; ++ } ++ int newsize = array.length * 2; ++ if (newsize < pos + n) { ++ newsize = pos + n; ++ } ++ byte [] newarray = new byte[newsize]; ++ System.arraycopy(array, 0, newarray, 0, pos); ++ array = newarray; ++} ++ ++/** ++ * Resets the current position of the output stream to the specified index. ++ * @param index The new current position. ++ * @throws IllegalArgumentException The index is not within the output. ++ */ ++public void ++jump(int index) { ++ if (index > pos) { ++ throw new IllegalArgumentException("cannot jump past " + ++ "end of data"); ++ } ++ pos = index; ++} ++ ++/** ++ * Saves the current state of the output stream. ++ * @throws IllegalArgumentException The index is not within the output. ++ */ ++public void ++save() { ++ saved_pos = pos; ++} ++ ++/** ++ * Restores the input stream to its state before the call to {@link #save}. ++ */ ++public void ++restore() { ++ if (saved_pos < 0) { ++ throw new IllegalStateException("no previous state"); ++ } ++ pos = saved_pos; ++ saved_pos = -1; ++} ++ ++/** ++ * Writes an unsigned 8 bit value to the stream. ++ * @param val The value to be written ++ */ ++public void ++writeU8(int val) { ++ check(val, 8); ++ need(1); ++ array[pos++] = (byte)(val & 0xFF); ++} ++ ++/** ++ * Writes an unsigned 16 bit value to the stream. ++ * @param val The value to be written ++ */ ++public void ++writeU16(int val) { ++ check(val, 16); ++ need(2); ++ array[pos++] = (byte)((val >>> 8) & 0xFF); ++ array[pos++] = (byte)(val & 0xFF); ++} ++ ++/** ++ * Writes an unsigned 16 bit value to the specified position in the stream. ++ * @param val The value to be written ++ * @param where The position to write the value. ++ */ ++public void ++writeU16At(int val, int where) { ++ check(val, 16); ++ if (where > pos - 2) ++ throw new IllegalArgumentException("cannot write past " + ++ "end of data"); ++ array[where++] = (byte)((val >>> 8) & 0xFF); ++ array[where++] = (byte)(val & 0xFF); ++} ++ ++/** ++ * Writes an unsigned 32 bit value to the stream. ++ * @param val The value to be written ++ */ ++public void ++writeU32(long val) { ++ check(val, 32); ++ need(4); ++ array[pos++] = (byte)((val >>> 24) & 0xFF); ++ array[pos++] = (byte)((val >>> 16) & 0xFF); ++ array[pos++] = (byte)((val >>> 8) & 0xFF); ++ array[pos++] = (byte)(val & 0xFF); ++} ++ ++/** ++ * Writes a byte array to the stream. ++ * @param b The array to write. ++ * @param off The offset of the array to start copying data from. ++ * @param len The number of bytes to write. ++ */ ++public void ++writeByteArray(byte [] b, int off, int len) { ++ need(len); ++ System.arraycopy(b, off, array, pos, len); ++ pos += len; ++} ++ ++/** ++ * Writes a byte array to the stream. ++ * @param b The array to write. ++ */ ++public void ++writeByteArray(byte [] b) { ++ writeByteArray(b, 0, b.length); ++} ++ ++/** ++ * Writes a counted string from the stream. A counted string is a one byte ++ * value indicating string length, followed by bytes of data. ++ * @param s The string to write. ++ */ ++public void ++writeCountedString(byte [] s) { ++ if (s.length > 0xFF) { ++ throw new IllegalArgumentException("Invalid counted string"); ++ } ++ need(1 + s.length); ++ array[pos++] = (byte)(s.length & 0xFF); ++ writeByteArray(s, 0, s.length); ++} ++ ++/** ++ * Returns a byte array containing the current contents of the stream. ++ */ ++public byte [] ++toByteArray() { ++ byte [] out = new byte[pos]; ++ System.arraycopy(array, 0, out, 0, pos); ++ return out; ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/DNSSEC.java b/external/asmack/build/src/trunk/org/xbill/DNS/DNSSEC.java +new file mode 100644 +index 0000000..082c9e9 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/DNSSEC.java +@@ -0,0 +1,824 @@ ++// Copyright (c) 1999-2010 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.math.*; ++import java.security.*; ++import java.security.interfaces.*; ++import java.security.spec.*; ++import java.util.*; ++ ++/** ++ * Constants and methods relating to DNSSEC. ++ * ++ * DNSSEC provides authentication for DNS information. ++ * @see RRSIGRecord ++ * @see DNSKEYRecord ++ * @see RRset ++ * ++ * @author Brian Wellington ++ */ ++ ++public class DNSSEC { ++ ++public static class Algorithm { ++ private Algorithm() {} ++ ++ /** RSA/MD5 public key (deprecated) */ ++ public static final int RSAMD5 = 1; ++ ++ /** Diffie Hellman key */ ++ public static final int DH = 2; ++ ++ /** DSA public key */ ++ public static final int DSA = 3; ++ ++ /** Elliptic Curve key */ ++ public static final int ECC = 4; ++ ++ /** RSA/SHA1 public key */ ++ public static final int RSASHA1 = 5; ++ ++ /** DSA/SHA1, NSEC3-aware public key */ ++ public static final int DSA_NSEC3_SHA1 = 6; ++ ++ /** RSA/SHA1, NSEC3-aware public key */ ++ public static final int RSA_NSEC3_SHA1 = 7; ++ ++ /** RSA/SHA256 public key */ ++ public static final int RSASHA256 = 8; ++ ++ /** RSA/SHA512 public key */ ++ public static final int RSASHA512 = 10; ++ ++ /** Indirect keys; the actual key is elsewhere. */ ++ public static final int INDIRECT = 252; ++ ++ /** Private algorithm, specified by domain name */ ++ public static final int PRIVATEDNS = 253; ++ ++ /** Private algorithm, specified by OID */ ++ public static final int PRIVATEOID = 254; ++ ++ private static Mnemonic algs = new Mnemonic("DNSSEC algorithm", ++ Mnemonic.CASE_UPPER); ++ ++ static { ++ algs.setMaximum(0xFF); ++ algs.setNumericAllowed(true); ++ ++ algs.add(RSAMD5, "RSAMD5"); ++ algs.add(DH, "DH"); ++ algs.add(DSA, "DSA"); ++ algs.add(ECC, "ECC"); ++ algs.add(RSASHA1, "RSASHA1"); ++ algs.add(DSA_NSEC3_SHA1, "DSA-NSEC3-SHA1"); ++ algs.add(RSA_NSEC3_SHA1, "RSA-NSEC3-SHA1"); ++ algs.add(RSASHA256, "RSASHA256"); ++ algs.add(RSASHA512, "RSASHA512"); ++ algs.add(INDIRECT, "INDIRECT"); ++ algs.add(PRIVATEDNS, "PRIVATEDNS"); ++ algs.add(PRIVATEOID, "PRIVATEOID"); ++ } ++ ++ /** ++ * Converts an algorithm into its textual representation ++ */ ++ public static String ++ string(int alg) { ++ return algs.getText(alg); ++ } ++ ++ /** ++ * Converts a textual representation of an algorithm into its numeric ++ * code. Integers in the range 0..255 are also accepted. ++ * @param s The textual representation of the algorithm ++ * @return The algorithm code, or -1 on error. ++ */ ++ public static int ++ value(String s) { ++ return algs.getValue(s); ++ } ++} ++ ++private ++DNSSEC() { } ++ ++private static void ++digestSIG(DNSOutput out, SIGBase sig) { ++ out.writeU16(sig.getTypeCovered()); ++ out.writeU8(sig.getAlgorithm()); ++ out.writeU8(sig.getLabels()); ++ out.writeU32(sig.getOrigTTL()); ++ out.writeU32(sig.getExpire().getTime() / 1000); ++ out.writeU32(sig.getTimeSigned().getTime() / 1000); ++ out.writeU16(sig.getFootprint()); ++ sig.getSigner().toWireCanonical(out); ++} ++ ++/** ++ * Creates a byte array containing the concatenation of the fields of the ++ * SIG record and the RRsets to be signed/verified. This does not perform ++ * a cryptographic digest. ++ * @param rrsig The RRSIG record used to sign/verify the rrset. ++ * @param rrset The data to be signed/verified. ++ * @return The data to be cryptographically signed or verified. ++ */ ++public static byte [] ++digestRRset(RRSIGRecord rrsig, RRset rrset) { ++ DNSOutput out = new DNSOutput(); ++ digestSIG(out, rrsig); ++ ++ int size = rrset.size(); ++ Record [] records = new Record[size]; ++ ++ Iterator it = rrset.rrs(); ++ Name name = rrset.getName(); ++ Name wild = null; ++ int sigLabels = rrsig.getLabels() + 1; // Add the root label back. ++ if (name.labels() > sigLabels) ++ wild = name.wild(name.labels() - sigLabels); ++ while (it.hasNext()) ++ records[--size] = (Record) it.next(); ++ Arrays.sort(records); ++ ++ DNSOutput header = new DNSOutput(); ++ if (wild != null) ++ wild.toWireCanonical(header); ++ else ++ name.toWireCanonical(header); ++ header.writeU16(rrset.getType()); ++ header.writeU16(rrset.getDClass()); ++ header.writeU32(rrsig.getOrigTTL()); ++ for (int i = 0; i < records.length; i++) { ++ out.writeByteArray(header.toByteArray()); ++ int lengthPosition = out.current(); ++ out.writeU16(0); ++ out.writeByteArray(records[i].rdataToWireCanonical()); ++ int rrlength = out.current() - lengthPosition - 2; ++ out.save(); ++ out.jump(lengthPosition); ++ out.writeU16(rrlength); ++ out.restore(); ++ } ++ return out.toByteArray(); ++} ++ ++/** ++ * Creates a byte array containing the concatenation of the fields of the ++ * SIG(0) record and the message to be signed. This does not perform ++ * a cryptographic digest. ++ * @param sig The SIG record used to sign the rrset. ++ * @param msg The message to be signed. ++ * @param previous If this is a response, the signature from the query. ++ * @return The data to be cryptographically signed. ++ */ ++public static byte [] ++digestMessage(SIGRecord sig, Message msg, byte [] previous) { ++ DNSOutput out = new DNSOutput(); ++ digestSIG(out, sig); ++ ++ if (previous != null) ++ out.writeByteArray(previous); ++ ++ msg.toWire(out); ++ return out.toByteArray(); ++} ++ ++/** ++ * A DNSSEC exception. ++ */ ++public static class DNSSECException extends Exception { ++ DNSSECException(String s) { ++ super(s); ++ } ++} ++ ++/** ++ * An algorithm is unsupported by this DNSSEC implementation. ++ */ ++public static class UnsupportedAlgorithmException extends DNSSECException { ++ UnsupportedAlgorithmException(int alg) { ++ super("Unsupported algorithm: " + alg); ++ } ++} ++ ++/** ++ * The cryptographic data in a DNSSEC key is malformed. ++ */ ++public static class MalformedKeyException extends DNSSECException { ++ MalformedKeyException(KEYBase rec) { ++ super("Invalid key data: " + rec.rdataToString()); ++ } ++} ++ ++/** ++ * A DNSSEC verification failed because fields in the DNSKEY and RRSIG records ++ * do not match. ++ */ ++public static class KeyMismatchException extends DNSSECException { ++ private KEYBase key; ++ private SIGBase sig; ++ ++ KeyMismatchException(KEYBase key, SIGBase sig) { ++ super("key " + ++ key.getName() + "/" + ++ DNSSEC.Algorithm.string(key.getAlgorithm()) + "/" + ++ key.getFootprint() + " " + ++ "does not match signature " + ++ sig.getSigner() + "/" + ++ DNSSEC.Algorithm.string(sig.getAlgorithm()) + "/" + ++ sig.getFootprint()); ++ } ++} ++ ++/** ++ * A DNSSEC verification failed because the signature has expired. ++ */ ++public static class SignatureExpiredException extends DNSSECException { ++ private Date when, now; ++ ++ SignatureExpiredException(Date when, Date now) { ++ super("signature expired"); ++ this.when = when; ++ this.now = now; ++ } ++ ++ /** ++ * @return When the signature expired ++ */ ++ public Date ++ getExpiration() { ++ return when; ++ } ++ ++ /** ++ * @return When the verification was attempted ++ */ ++ public Date ++ getVerifyTime() { ++ return now; ++ } ++} ++ ++/** ++ * A DNSSEC verification failed because the signature has not yet become valid. ++ */ ++public static class SignatureNotYetValidException extends DNSSECException { ++ private Date when, now; ++ ++ SignatureNotYetValidException(Date when, Date now) { ++ super("signature is not yet valid"); ++ this.when = when; ++ this.now = now; ++ } ++ ++ /** ++ * @return When the signature will become valid ++ */ ++ public Date ++ getExpiration() { ++ return when; ++ } ++ ++ /** ++ * @return When the verification was attempted ++ */ ++ public Date ++ getVerifyTime() { ++ return now; ++ } ++} ++ ++/** ++ * A DNSSEC verification failed because the cryptographic signature ++ * verification failed. ++ */ ++public static class SignatureVerificationException extends DNSSECException { ++ SignatureVerificationException() { ++ super("signature verification failed"); ++ } ++} ++ ++/** ++ * The key data provided is inconsistent. ++ */ ++public static class IncompatibleKeyException extends IllegalArgumentException { ++ IncompatibleKeyException() { ++ super("incompatible keys"); ++ } ++} ++ ++private static int ++BigIntegerLength(BigInteger i) { ++ return (i.bitLength() + 7) / 8; ++} ++ ++private static BigInteger ++readBigInteger(DNSInput in, int len) throws IOException { ++ byte [] b = in.readByteArray(len); ++ return new BigInteger(1, b); ++} ++ ++private static BigInteger ++readBigInteger(DNSInput in) { ++ byte [] b = in.readByteArray(); ++ return new BigInteger(1, b); ++} ++ ++private static void ++writeBigInteger(DNSOutput out, BigInteger val) { ++ byte [] b = val.toByteArray(); ++ if (b[0] == 0) ++ out.writeByteArray(b, 1, b.length - 1); ++ else ++ out.writeByteArray(b); ++} ++ ++private static PublicKey ++toRSAPublicKey(KEYBase r) throws IOException, GeneralSecurityException { ++ DNSInput in = new DNSInput(r.getKey()); ++ int exponentLength = in.readU8(); ++ if (exponentLength == 0) ++ exponentLength = in.readU16(); ++ BigInteger exponent = readBigInteger(in, exponentLength); ++ BigInteger modulus = readBigInteger(in); ++ ++ KeyFactory factory = KeyFactory.getInstance("RSA"); ++ return factory.generatePublic(new RSAPublicKeySpec(modulus, exponent)); ++} ++ ++private static PublicKey ++toDSAPublicKey(KEYBase r) throws IOException, GeneralSecurityException, ++ MalformedKeyException ++{ ++ DNSInput in = new DNSInput(r.getKey()); ++ ++ int t = in.readU8(); ++ if (t > 8) ++ throw new MalformedKeyException(r); ++ ++ BigInteger q = readBigInteger(in, 20); ++ BigInteger p = readBigInteger(in, 64 + t*8); ++ BigInteger g = readBigInteger(in, 64 + t*8); ++ BigInteger y = readBigInteger(in, 64 + t*8); ++ ++ KeyFactory factory = KeyFactory.getInstance("DSA"); ++ return factory.generatePublic(new DSAPublicKeySpec(y, p, q, g)); ++} ++ ++/** Converts a KEY/DNSKEY record into a PublicKey */ ++static PublicKey ++toPublicKey(KEYBase r) throws DNSSECException { ++ int alg = r.getAlgorithm(); ++ try { ++ switch (alg) { ++ case Algorithm.RSAMD5: ++ case Algorithm.RSASHA1: ++ case Algorithm.RSA_NSEC3_SHA1: ++ case Algorithm.RSASHA256: ++ case Algorithm.RSASHA512: ++ return toRSAPublicKey(r); ++ case Algorithm.DSA: ++ case Algorithm.DSA_NSEC3_SHA1: ++ return toDSAPublicKey(r); ++ default: ++ throw new UnsupportedAlgorithmException(alg); ++ } ++ } ++ catch (IOException e) { ++ throw new MalformedKeyException(r); ++ } ++ catch (GeneralSecurityException e) { ++ throw new DNSSECException(e.toString()); ++ } ++} ++ ++private static byte [] ++fromRSAPublicKey(RSAPublicKey key) { ++ DNSOutput out = new DNSOutput(); ++ BigInteger exponent = key.getPublicExponent(); ++ BigInteger modulus = key.getModulus(); ++ int exponentLength = BigIntegerLength(exponent); ++ ++ if (exponentLength < 256) ++ out.writeU8(exponentLength); ++ else { ++ out.writeU8(0); ++ out.writeU16(exponentLength); ++ } ++ writeBigInteger(out, exponent); ++ writeBigInteger(out, modulus); ++ ++ return out.toByteArray(); ++} ++ ++private static byte [] ++fromDSAPublicKey(DSAPublicKey key) { ++ DNSOutput out = new DNSOutput(); ++ BigInteger q = key.getParams().getQ(); ++ BigInteger p = key.getParams().getP(); ++ BigInteger g = key.getParams().getG(); ++ BigInteger y = key.getY(); ++ int t = (p.toByteArray().length - 64) / 8; ++ ++ out.writeU8(t); ++ writeBigInteger(out, q); ++ writeBigInteger(out, p); ++ writeBigInteger(out, g); ++ writeBigInteger(out, y); ++ ++ return out.toByteArray(); ++} ++ ++/** Builds a DNSKEY record from a PublicKey */ ++static byte [] ++fromPublicKey(PublicKey key, int alg) throws DNSSECException ++{ ++ byte [] data = null; ++ ++ switch (alg) { ++ case Algorithm.RSAMD5: ++ case Algorithm.RSASHA1: ++ case Algorithm.RSA_NSEC3_SHA1: ++ case Algorithm.RSASHA256: ++ case Algorithm.RSASHA512: ++ if (! (key instanceof RSAPublicKey)) ++ throw new IncompatibleKeyException(); ++ return fromRSAPublicKey((RSAPublicKey) key); ++ case Algorithm.DSA: ++ case Algorithm.DSA_NSEC3_SHA1: ++ if (! (key instanceof DSAPublicKey)) ++ throw new IncompatibleKeyException(); ++ return fromDSAPublicKey((DSAPublicKey) key); ++ default: ++ throw new UnsupportedAlgorithmException(alg); ++ } ++} ++ ++/** ++ * Convert an algorithm number to the corresponding JCA string. ++ * @param alg The algorithm number. ++ * @throws UnsupportedAlgorithmException The algorithm is unknown. ++ */ ++public static String ++algString(int alg) throws UnsupportedAlgorithmException { ++ switch (alg) { ++ case Algorithm.RSAMD5: ++ return "MD5withRSA"; ++ case Algorithm.DSA: ++ case Algorithm.DSA_NSEC3_SHA1: ++ return "SHA1withDSA"; ++ case Algorithm.RSASHA1: ++ case Algorithm.RSA_NSEC3_SHA1: ++ return "SHA1withRSA"; ++ case Algorithm.RSASHA256: ++ return "SHA256withRSA"; ++ case Algorithm.RSASHA512: ++ return "SHA512withRSA"; ++ default: ++ throw new UnsupportedAlgorithmException(alg); ++ } ++} ++ ++private static final int ASN1_SEQ = 0x30; ++private static final int ASN1_INT = 0x2; ++ ++private static final int DSA_LEN = 20; ++ ++private static byte [] ++DSASignaturefromDNS(byte [] dns) throws DNSSECException, IOException { ++ if (dns.length != 1 + DSA_LEN * 2) ++ throw new SignatureVerificationException(); ++ ++ DNSInput in = new DNSInput(dns); ++ DNSOutput out = new DNSOutput(); ++ ++ int t = in.readU8(); ++ ++ byte [] r = in.readByteArray(DSA_LEN); ++ int rlen = DSA_LEN; ++ if (r[0] < 0) ++ rlen++; ++ ++ byte [] s = in.readByteArray(DSA_LEN); ++ int slen = DSA_LEN; ++ if (s[0] < 0) ++ slen++; ++ ++ out.writeU8(ASN1_SEQ); ++ out.writeU8(rlen + slen + 4); ++ ++ out.writeU8(ASN1_INT); ++ out.writeU8(rlen); ++ if (rlen > DSA_LEN) ++ out.writeU8(0); ++ out.writeByteArray(r); ++ ++ out.writeU8(ASN1_INT); ++ out.writeU8(slen); ++ if (slen > DSA_LEN) ++ out.writeU8(0); ++ out.writeByteArray(s); ++ ++ return out.toByteArray(); ++} ++ ++private static byte [] ++DSASignaturetoDNS(byte [] key, int t) throws IOException { ++ DNSInput in = new DNSInput(key); ++ DNSOutput out = new DNSOutput(); ++ ++ out.writeU8(t); ++ ++ int tmp = in.readU8(); ++ if (tmp != ASN1_SEQ) ++ throw new IOException(); ++ int seqlen = in.readU8(); ++ ++ tmp = in.readU8(); ++ if (tmp != ASN1_INT) ++ throw new IOException(); ++ int rlen = in.readU8(); ++ if (rlen == DSA_LEN + 1) { ++ if (in.readU8() != 0) ++ throw new IOException(); ++ } else if (rlen != DSA_LEN) ++ throw new IOException(); ++ byte [] bytes = in.readByteArray(DSA_LEN); ++ out.writeByteArray(bytes); ++ ++ tmp = in.readU8(); ++ if (tmp != ASN1_INT) ++ throw new IOException(); ++ int slen = in.readU8(); ++ if (slen == DSA_LEN + 1) { ++ if (in.readU8() != 0) ++ throw new IOException(); ++ } else if (slen != DSA_LEN) ++ throw new IOException(); ++ bytes = in.readByteArray(DSA_LEN); ++ out.writeByteArray(bytes); ++ ++ return out.toByteArray(); ++} ++ ++private static void ++verify(PublicKey key, int alg, byte [] data, byte [] signature) ++throws DNSSECException ++{ ++ if (key instanceof DSAPublicKey) { ++ try { ++ signature = DSASignaturefromDNS(signature); ++ } ++ catch (IOException e) { ++ throw new IllegalStateException(); ++ } ++ } ++ ++ try { ++ Signature s = Signature.getInstance(algString(alg)); ++ s.initVerify(key); ++ s.update(data); ++ if (!s.verify(signature)) ++ throw new SignatureVerificationException(); ++ } ++ catch (GeneralSecurityException e) { ++ throw new DNSSECException(e.toString()); ++ } ++} ++ ++private static boolean ++matches(SIGBase sig, KEYBase key) ++{ ++ return (key.getAlgorithm() == sig.getAlgorithm() && ++ key.getFootprint() == sig.getFootprint() && ++ key.getName().equals(sig.getSigner())); ++} ++ ++/** ++ * Verify a DNSSEC signature. ++ * @param rrset The data to be verified. ++ * @param rrsig The RRSIG record containing the signature. ++ * @param key The DNSKEY record to verify the signature with. ++ * @throws UnsupportedAlgorithmException The algorithm is unknown ++ * @throws MalformedKeyException The key is malformed ++ * @throws KeyMismatchException The key and signature do not match ++ * @throws SignatureExpiredException The signature has expired ++ * @throws SignatureNotYetValidException The signature is not yet valid ++ * @throws SignatureVerificationException The signature does not verify. ++ * @throws DNSSECException Some other error occurred. ++ */ ++public static void ++verify(RRset rrset, RRSIGRecord rrsig, DNSKEYRecord key) throws DNSSECException ++{ ++ if (!matches(rrsig, key)) ++ throw new KeyMismatchException(key, rrsig); ++ ++ Date now = new Date(); ++ if (now.compareTo(rrsig.getExpire()) > 0) ++ throw new SignatureExpiredException(rrsig.getExpire(), now); ++ if (now.compareTo(rrsig.getTimeSigned()) < 0) ++ throw new SignatureNotYetValidException(rrsig.getTimeSigned(), ++ now); ++ ++ verify(key.getPublicKey(), rrsig.getAlgorithm(), ++ digestRRset(rrsig, rrset), rrsig.getSignature()); ++} ++ ++private static byte [] ++sign(PrivateKey privkey, PublicKey pubkey, int alg, byte [] data, ++ String provider) throws DNSSECException ++{ ++ byte [] signature; ++ try { ++ Signature s; ++ if (provider != null) ++ s = Signature.getInstance(algString(alg), provider); ++ else ++ s = Signature.getInstance(algString(alg)); ++ s.initSign(privkey); ++ s.update(data); ++ signature = s.sign(); ++ } ++ catch (GeneralSecurityException e) { ++ throw new DNSSECException(e.toString()); ++ } ++ ++ if (pubkey instanceof DSAPublicKey) { ++ try { ++ DSAPublicKey dsa = (DSAPublicKey) pubkey; ++ BigInteger P = dsa.getParams().getP(); ++ int t = (BigIntegerLength(P) - 64) / 8; ++ signature = DSASignaturetoDNS(signature, t); ++ } ++ catch (IOException e) { ++ throw new IllegalStateException(); ++ } ++ } ++ ++ return signature; ++} ++ ++static void ++checkAlgorithm(PrivateKey key, int alg) throws UnsupportedAlgorithmException ++{ ++ switch (alg) { ++ case Algorithm.RSAMD5: ++ case Algorithm.RSASHA1: ++ case Algorithm.RSA_NSEC3_SHA1: ++ case Algorithm.RSASHA256: ++ case Algorithm.RSASHA512: ++ if (! (key instanceof RSAPrivateKey)) ++ throw new IncompatibleKeyException(); ++ break; ++ case Algorithm.DSA: ++ case Algorithm.DSA_NSEC3_SHA1: ++ if (! (key instanceof DSAPrivateKey)) ++ throw new IncompatibleKeyException(); ++ break; ++ default: ++ throw new UnsupportedAlgorithmException(alg); ++ } ++} ++ ++/** ++ * Generate a DNSSEC signature. key and privateKey must refer to the ++ * same underlying cryptographic key. ++ * @param rrset The data to be signed ++ * @param key The DNSKEY record to use as part of signing ++ * @param privkey The PrivateKey to use when signing ++ * @param inception The time at which the signatures should become valid ++ * @param expiration The time at which the signatures should expire ++ * @throws UnsupportedAlgorithmException The algorithm is unknown ++ * @throws MalformedKeyException The key is malformed ++ * @throws DNSSECException Some other error occurred. ++ * @return The generated signature ++ */ ++public static RRSIGRecord ++sign(RRset rrset, DNSKEYRecord key, PrivateKey privkey, ++ Date inception, Date expiration) throws DNSSECException ++{ ++ return sign(rrset, key, privkey, inception, expiration, null); ++} ++ ++/** ++ * Generate a DNSSEC signature. key and privateKey must refer to the ++ * same underlying cryptographic key. ++ * @param rrset The data to be signed ++ * @param key The DNSKEY record to use as part of signing ++ * @param privkey The PrivateKey to use when signing ++ * @param inception The time at which the signatures should become valid ++ * @param expiration The time at which the signatures should expire ++ * @param provider The name of the JCA provider. If non-null, it will be ++ * passed to JCA getInstance() methods. ++ * @throws UnsupportedAlgorithmException The algorithm is unknown ++ * @throws MalformedKeyException The key is malformed ++ * @throws DNSSECException Some other error occurred. ++ * @return The generated signature ++ */ ++public static RRSIGRecord ++sign(RRset rrset, DNSKEYRecord key, PrivateKey privkey, ++ Date inception, Date expiration, String provider) throws DNSSECException ++{ ++ int alg = key.getAlgorithm(); ++ checkAlgorithm(privkey, alg); ++ ++ RRSIGRecord rrsig = new RRSIGRecord(rrset.getName(), rrset.getDClass(), ++ rrset.getTTL(), rrset.getType(), ++ alg, rrset.getTTL(), ++ expiration, inception, ++ key.getFootprint(), ++ key.getName(), null); ++ ++ rrsig.setSignature(sign(privkey, key.getPublicKey(), alg, ++ digestRRset(rrsig, rrset), provider)); ++ return rrsig; ++} ++ ++static SIGRecord ++signMessage(Message message, SIGRecord previous, KEYRecord key, ++ PrivateKey privkey, Date inception, Date expiration) ++ throws DNSSECException ++{ ++ int alg = key.getAlgorithm(); ++ checkAlgorithm(privkey, alg); ++ ++ SIGRecord sig = new SIGRecord(Name.root, DClass.ANY, 0, 0, ++ alg, 0, expiration, inception, ++ key.getFootprint(), ++ key.getName(), null); ++ DNSOutput out = new DNSOutput(); ++ digestSIG(out, sig); ++ if (previous != null) ++ out.writeByteArray(previous.getSignature()); ++ message.toWire(out); ++ ++ sig.setSignature(sign(privkey, key.getPublicKey(), ++ alg, out.toByteArray(), null)); ++ return sig; ++} ++ ++static void ++verifyMessage(Message message, byte [] bytes, SIGRecord sig, SIGRecord previous, ++ KEYRecord key) throws DNSSECException ++{ ++ if (!matches(sig, key)) ++ throw new KeyMismatchException(key, sig); ++ ++ Date now = new Date(); ++ if (now.compareTo(sig.getExpire()) > 0) ++ throw new SignatureExpiredException(sig.getExpire(), now); ++ if (now.compareTo(sig.getTimeSigned()) < 0) ++ throw new SignatureNotYetValidException(sig.getTimeSigned(), ++ now); ++ ++ DNSOutput out = new DNSOutput(); ++ digestSIG(out, sig); ++ if (previous != null) ++ out.writeByteArray(previous.getSignature()); ++ ++ Header header = (Header) message.getHeader().clone(); ++ header.decCount(Section.ADDITIONAL); ++ out.writeByteArray(header.toWire()); ++ ++ out.writeByteArray(bytes, Header.LENGTH, ++ message.sig0start - Header.LENGTH); ++ ++ verify(key.getPublicKey(), sig.getAlgorithm(), ++ out.toByteArray(), sig.getSignature()); ++} ++ ++/** ++ * Generate the digest value for a DS key ++ * @param key Which is covered by the DS record ++ * @param digestid The type of digest ++ * @return The digest value as an array of bytes ++ */ ++static byte [] ++generateDSDigest(DNSKEYRecord key, int digestid) ++{ ++ MessageDigest digest; ++ try { ++ switch (digestid) { ++ case DSRecord.Digest.SHA1: ++ digest = MessageDigest.getInstance("sha-1"); ++ break; ++ case DSRecord.Digest.SHA256: ++ digest = MessageDigest.getInstance("sha-256"); ++ break; ++ default: ++ throw new IllegalArgumentException( ++ "unknown DS digest type " + digestid); ++ } ++ } ++ catch (NoSuchAlgorithmException e) { ++ throw new IllegalStateException("no message digest support"); ++ } ++ digest.update(key.getName().toWire()); ++ digest.update(key.rdataToWireCanonical()); ++ return digest.digest(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/DSRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/DSRecord.java +new file mode 100644 +index 0000000..8058658 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/DSRecord.java +@@ -0,0 +1,153 @@ ++// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import org.xbill.DNS.utils.*; ++ ++/** ++ * DS - contains a Delegation Signer record, which acts as a ++ * placeholder for KEY records in the parent zone. ++ * @see DNSSEC ++ * ++ * @author David Blacka ++ * @author Brian Wellington ++ */ ++ ++public class DSRecord extends Record { ++ ++public static class Digest { ++ private Digest() {} ++ ++ /** SHA-1 */ ++ public static final int SHA1 = 1; ++ ++ /** SHA-256 */ ++ public static final int SHA256 = 2; ++} ++ ++public static final int SHA1_DIGEST_ID = Digest.SHA1; ++public static final int SHA256_DIGEST_ID = Digest.SHA256; ++ ++private static final long serialVersionUID = -9001819329700081493L; ++ ++private int footprint; ++private int alg; ++private int digestid; ++private byte [] digest; ++ ++DSRecord() {} ++ ++Record ++getObject() { ++ return new DSRecord(); ++} ++ ++/** ++ * Creates a DS Record from the given data ++ * @param footprint The original KEY record's footprint (keyid). ++ * @param alg The original key algorithm. ++ * @param digestid The digest id code. ++ * @param digest A hash of the original key. ++ */ ++public ++DSRecord(Name name, int dclass, long ttl, int footprint, int alg, ++ int digestid, byte [] digest) ++{ ++ super(name, Type.DS, dclass, ttl); ++ this.footprint = checkU16("footprint", footprint); ++ this.alg = checkU8("alg", alg); ++ this.digestid = checkU8("digestid", digestid); ++ this.digest = digest; ++} ++ ++/** ++ * Creates a DS Record from the given data ++ * @param digestid The digest id code. ++ * @param key The key to digest ++ */ ++public ++DSRecord(Name name, int dclass, long ttl, int digestid, DNSKEYRecord key) ++{ ++ this(name, dclass, ttl, key.getFootprint(), key.getAlgorithm(), ++ digestid, DNSSEC.generateDSDigest(key, digestid)); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ footprint = in.readU16(); ++ alg = in.readU8(); ++ digestid = in.readU8(); ++ digest = in.readByteArray(); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ footprint = st.getUInt16(); ++ alg = st.getUInt8(); ++ digestid = st.getUInt8(); ++ digest = st.getHex(); ++} ++ ++/** ++ * Converts rdata to a String ++ */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(footprint); ++ sb.append(" "); ++ sb.append(alg); ++ sb.append(" "); ++ sb.append(digestid); ++ if (digest != null) { ++ sb.append(" "); ++ sb.append(base16.toString(digest)); ++ } ++ ++ return sb.toString(); ++} ++ ++/** ++ * Returns the key's algorithm. ++ */ ++public int ++getAlgorithm() { ++ return alg; ++} ++ ++/** ++ * Returns the key's Digest ID. ++ */ ++public int ++getDigestID() ++{ ++ return digestid; ++} ++ ++/** ++ * Returns the binary hash of the key. ++ */ ++public byte [] ++getDigest() { ++ return digest; ++} ++ ++/** ++ * Returns the key's footprint. ++ */ ++public int ++getFootprint() { ++ return footprint; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeU16(footprint); ++ out.writeU8(alg); ++ out.writeU8(digestid); ++ if (digest != null) ++ out.writeByteArray(digest); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/EDNSOption.java b/external/asmack/build/src/trunk/org/xbill/DNS/EDNSOption.java +new file mode 100644 +index 0000000..a65bd19 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/EDNSOption.java +@@ -0,0 +1,215 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.util.Arrays; ++ ++/** ++ * DNS extension options, as described in RFC 2671. The rdata of an OPT record ++ * is defined as a list of options; this represents a single option. ++ * ++ * @author Brian Wellington ++ * @author Ming Zhou <mizhou@bnivideo.com>, Beaumaris Networks ++ */ ++public abstract class EDNSOption { ++ ++public static class Code { ++ private Code() {} ++ ++ /** Name Server Identifier, RFC 5001 */ ++ public final static int NSID = 3; ++ ++ /** Client Subnet, defined in draft-vandergaast-edns-client-subnet-00 */ ++ public final static int CLIENT_SUBNET = 20730; ++ ++ private static Mnemonic codes = new Mnemonic("EDNS Option Codes", ++ Mnemonic.CASE_UPPER); ++ ++ static { ++ codes.setMaximum(0xFFFF); ++ codes.setPrefix("CODE"); ++ codes.setNumericAllowed(true); ++ ++ codes.add(NSID, "NSID"); ++ codes.add(CLIENT_SUBNET, "CLIENT_SUBNET"); ++ } ++ ++ /** ++ * Converts an EDNS Option Code into its textual representation ++ */ ++ public static String ++ string(int code) { ++ return codes.getText(code); ++ } ++ ++ /** ++ * Converts a textual representation of an EDNS Option Code into its ++ * numeric value. ++ * @param s The textual representation of the option code ++ * @return The option code, or -1 on error. ++ */ ++ public static int ++ value(String s) { ++ return codes.getValue(s); ++ } ++} ++ ++private final int code; ++ ++/** ++ * ++ * Creates an option with the given option code and data. ++ */ ++public ++EDNSOption(int code) { ++ this.code = Record.checkU16("code", code); ++} ++ ++public String ++toString() { ++ StringBuffer sb = new StringBuffer(); ++ ++ sb.append("{"); ++ sb.append(EDNSOption.Code.string(code)); ++ sb.append(": "); ++ sb.append(optionToString()); ++ sb.append("}"); ++ ++ return sb.toString(); ++} ++ ++/** ++ * Returns the EDNS Option's code. ++ * ++ * @return the option code ++ */ ++public int ++getCode() { ++ return code; ++} ++ ++/** ++ * Returns the EDNS Option's data, as a byte array. ++ * ++ * @return the option data ++ */ ++byte [] ++getData() { ++ DNSOutput out = new DNSOutput(); ++ optionToWire(out); ++ return out.toByteArray(); ++} ++ ++/** ++ * Converts the wire format of an EDNS Option (the option data only) into the ++ * type-specific format. ++ * @param in The input Stream. ++ */ ++abstract void ++optionFromWire(DNSInput in) throws IOException; ++ ++/** ++ * Converts the wire format of an EDNS Option (including code and length) into ++ * the type-specific format. ++ * @param out The input stream. ++ */ ++static EDNSOption ++fromWire(DNSInput in) throws IOException { ++ int code, length; ++ ++ code = in.readU16(); ++ length = in.readU16(); ++ if (in.remaining() < length) ++ throw new WireParseException("truncated option"); ++ int save = in.saveActive(); ++ in.setActive(length); ++ EDNSOption option; ++ switch (code) { ++ case Code.NSID: ++ option = new NSIDOption(); ++ break; ++ case Code.CLIENT_SUBNET: ++ option = new ClientSubnetOption(); ++ break; ++ default: ++ option = new GenericEDNSOption(code); ++ break; ++ } ++ option.optionFromWire(in); ++ in.restoreActive(save); ++ ++ return option; ++} ++ ++/** ++ * Converts the wire format of an EDNS Option (including code and length) into ++ * the type-specific format. ++ * @return The option, in wire format. ++ */ ++public static EDNSOption ++fromWire(byte [] b) throws IOException { ++ return fromWire(new DNSInput(b)); ++} ++ ++/** ++ * Converts an EDNS Option (the type-specific option data only) into wire format. ++ * @param out The output stream. ++ */ ++abstract void ++optionToWire(DNSOutput out); ++ ++/** ++ * Converts an EDNS Option (including code and length) into wire format. ++ * @param out The output stream. ++ */ ++void ++toWire(DNSOutput out) { ++ out.writeU16(code); ++ int lengthPosition = out.current(); ++ out.writeU16(0); /* until we know better */ ++ optionToWire(out); ++ int length = out.current() - lengthPosition - 2; ++ out.writeU16At(length, lengthPosition); ++} ++ ++/** ++ * Converts an EDNS Option (including code and length) into wire format. ++ * @return The option, in wire format. ++ */ ++public byte [] ++toWire() throws IOException { ++ DNSOutput out = new DNSOutput(); ++ toWire(out); ++ return out.toByteArray(); ++} ++ ++/** ++ * Determines if two EDNS Options are identical. ++ * @param arg The option to compare to ++ * @return true if the options are equal, false otherwise. ++ */ ++public boolean ++equals(Object arg) { ++ if (arg == null || !(arg instanceof EDNSOption)) ++ return false; ++ EDNSOption opt = (EDNSOption) arg; ++ if (code != opt.code) ++ return false; ++ return Arrays.equals(getData(), opt.getData()); ++} ++ ++/** ++ * Generates a hash code based on the EDNS Option's data. ++ */ ++public int ++hashCode() { ++ byte [] array = getData(); ++ int hashval = 0; ++ for (int i = 0; i < array.length; i++) ++ hashval += ((hashval << 3) + (array[i] & 0xFF)); ++ return hashval; ++} ++ ++abstract String optionToString(); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/EmptyRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/EmptyRecord.java +new file mode 100644 +index 0000000..f5e61e8 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/EmptyRecord.java +@@ -0,0 +1,42 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++/** ++ * A class implementing Records with no data; that is, records used in ++ * the question section of messages and meta-records in dynamic update. ++ * ++ * @author Brian Wellington ++ */ ++ ++class EmptyRecord extends Record { ++ ++private static final long serialVersionUID = 3601852050646429582L; ++ ++EmptyRecord() {} ++ ++Record ++getObject() { ++ return new EmptyRecord(); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++} ++ ++String ++rrToString() { ++ return ""; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ExtendedFlags.java b/external/asmack/build/src/trunk/org/xbill/DNS/ExtendedFlags.java +new file mode 100644 +index 0000000..8f3bbab +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/ExtendedFlags.java +@@ -0,0 +1,45 @@ ++// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Constants and functions relating to EDNS flags. ++ * ++ * @author Brian Wellington ++ */ ++ ++public final class ExtendedFlags { ++ ++private static Mnemonic extflags = new Mnemonic("EDNS Flag", ++ Mnemonic.CASE_LOWER); ++ ++/** dnssec ok */ ++public static final int DO = 0x8000; ++ ++static { ++ extflags.setMaximum(0xFFFF); ++ extflags.setPrefix("FLAG"); ++ extflags.setNumericAllowed(true); ++ ++ extflags.add(DO, "do"); ++} ++ ++private ++ExtendedFlags() {} ++ ++/** Converts a numeric extended flag into a String */ ++public static String ++string(int i) { ++ return extflags.getText(i); ++} ++ ++/** ++ * Converts a textual representation of an extended flag into its numeric ++ * value ++ */ ++public static int ++value(String s) { ++ return extflags.getValue(s); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ExtendedResolver.java b/external/asmack/build/src/trunk/org/xbill/DNS/ExtendedResolver.java +new file mode 100644 +index 0000000..f762b84 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/ExtendedResolver.java +@@ -0,0 +1,419 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.util.*; ++import java.io.*; ++import java.net.*; ++ ++/** ++ * An implementation of Resolver that can send queries to multiple servers, ++ * sending the queries multiple times if necessary. ++ * @see Resolver ++ * ++ * @author Brian Wellington ++ */ ++ ++public class ExtendedResolver implements Resolver { ++ ++private static class Resolution implements ResolverListener { ++ Resolver [] resolvers; ++ int [] sent; ++ Object [] inprogress; ++ int retries; ++ int outstanding; ++ boolean done; ++ Message query; ++ Message response; ++ Throwable thrown; ++ ResolverListener listener; ++ ++ public ++ Resolution(ExtendedResolver eres, Message query) { ++ List l = eres.resolvers; ++ resolvers = (Resolver []) l.toArray (new Resolver[l.size()]); ++ if (eres.loadBalance) { ++ int nresolvers = resolvers.length; ++ /* ++ * Note: this is not synchronized, since the ++ * worst thing that can happen is a random ++ * ordering, which is ok. ++ */ ++ int start = eres.lbStart++ % nresolvers; ++ if (eres.lbStart > nresolvers) ++ eres.lbStart %= nresolvers; ++ if (start > 0) { ++ Resolver [] shuffle = new Resolver[nresolvers]; ++ for (int i = 0; i < nresolvers; i++) { ++ int pos = (i + start) % nresolvers; ++ shuffle[i] = resolvers[pos]; ++ } ++ resolvers = shuffle; ++ } ++ } ++ sent = new int[resolvers.length]; ++ inprogress = new Object[resolvers.length]; ++ retries = eres.retries; ++ this.query = query; ++ } ++ ++ /* Asynchronously sends a message. */ ++ public void ++ send(int n) { ++ sent[n]++; ++ outstanding++; ++ try { ++ inprogress[n] = resolvers[n].sendAsync(query, this); ++ } ++ catch (Throwable t) { ++ synchronized (this) { ++ thrown = t; ++ done = true; ++ if (listener == null) { ++ notifyAll(); ++ return; ++ } ++ } ++ } ++ } ++ ++ /* Start a synchronous resolution */ ++ public Message ++ start() throws IOException { ++ try { ++ /* ++ * First, try sending synchronously. If this works, ++ * we're done. Otherwise, we'll get an exception ++ * and continue. It would be easier to call send(0), ++ * but this avoids a thread creation. If and when ++ * SimpleResolver.sendAsync() can be made to not ++ * create a thread, this could be changed. ++ */ ++ sent[0]++; ++ outstanding++; ++ inprogress[0] = new Object(); ++ return resolvers[0].send(query); ++ } ++ catch (Exception e) { ++ /* ++ * This will either cause more queries to be sent ++ * asynchronously or will set the 'done' flag. ++ */ ++ handleException(inprogress[0], e); ++ } ++ /* ++ * Wait for a successful response or for each ++ * subresolver to fail. ++ */ ++ synchronized (this) { ++ while (!done) { ++ try { ++ wait(); ++ } ++ catch (InterruptedException e) { ++ } ++ } ++ } ++ /* Return the response or throw an exception */ ++ if (response != null) ++ return response; ++ else if (thrown instanceof IOException) ++ throw (IOException) thrown; ++ else if (thrown instanceof RuntimeException) ++ throw (RuntimeException) thrown; ++ else if (thrown instanceof Error) ++ throw (Error) thrown; ++ else ++ throw new IllegalStateException ++ ("ExtendedResolver failure"); ++ } ++ ++ /* Start an asynchronous resolution */ ++ public void ++ startAsync(ResolverListener listener) { ++ this.listener = listener; ++ send(0); ++ } ++ ++ /* ++ * Receive a response. If the resolution hasn't been completed, ++ * either wake up the blocking thread or call the callback. ++ */ ++ public void ++ receiveMessage(Object id, Message m) { ++ if (Options.check("verbose")) ++ System.err.println("ExtendedResolver: " + ++ "received message"); ++ synchronized (this) { ++ if (done) ++ return; ++ response = m; ++ done = true; ++ if (listener == null) { ++ notifyAll(); ++ return; ++ } ++ } ++ listener.receiveMessage(this, response); ++ } ++ ++ /* ++ * Receive an exception. If the resolution has been completed, ++ * do nothing. Otherwise make progress. ++ */ ++ public void ++ handleException(Object id, Exception e) { ++ if (Options.check("verbose")) ++ System.err.println("ExtendedResolver: got " + e); ++ synchronized (this) { ++ outstanding--; ++ if (done) ++ return; ++ int n; ++ for (n = 0; n < inprogress.length; n++) ++ if (inprogress[n] == id) ++ break; ++ /* If we don't know what this is, do nothing. */ ++ if (n == inprogress.length) ++ return; ++ boolean startnext = false; ++ /* ++ * If this is the first response from server n, ++ * we should start sending queries to server n + 1. ++ */ ++ if (sent[n] == 1 && n < resolvers.length - 1) ++ startnext = true; ++ if (e instanceof InterruptedIOException) { ++ /* Got a timeout; resend */ ++ if (sent[n] < retries) ++ send(n); ++ if (thrown == null) ++ thrown = e; ++ } else if (e instanceof SocketException) { ++ /* ++ * Problem with the socket; don't resend ++ * on it ++ */ ++ if (thrown == null || ++ thrown instanceof InterruptedIOException) ++ thrown = e; ++ } else { ++ /* ++ * Problem with the response; don't resend ++ * on the same socket. ++ */ ++ thrown = e; ++ } ++ if (done) ++ return; ++ if (startnext) ++ send(n + 1); ++ if (done) ++ return; ++ if (outstanding == 0) { ++ /* ++ * If we're done and this is synchronous, ++ * wake up the blocking thread. ++ */ ++ done = true; ++ if (listener == null) { ++ notifyAll(); ++ return; ++ } ++ } ++ if (!done) ++ return; ++ } ++ /* If we're done and this is asynchronous, call the callback. */ ++ if (!(thrown instanceof Exception)) ++ thrown = new RuntimeException(thrown.getMessage()); ++ listener.handleException(this, (Exception) thrown); ++ } ++} ++ ++private static final int quantum = 5; ++ ++private List resolvers; ++private boolean loadBalance = false; ++private int lbStart = 0; ++private int retries = 3; ++ ++private void ++init() { ++ resolvers = new ArrayList(); ++} ++ ++/** ++ * Creates a new Extended Resolver. The default ResolverConfig is used to ++ * determine the servers for which SimpleResolver contexts should be ++ * initialized. ++ * @see SimpleResolver ++ * @see ResolverConfig ++ * @exception UnknownHostException Failure occured initializing SimpleResolvers ++ */ ++public ++ExtendedResolver() throws UnknownHostException { ++ init(); ++ String [] servers = ResolverConfig.getCurrentConfig().servers(); ++ if (servers != null) { ++ for (int i = 0; i < servers.length; i++) { ++ Resolver r = new SimpleResolver(servers[i]); ++ r.setTimeout(quantum); ++ resolvers.add(r); ++ } ++ } ++ else ++ resolvers.add(new SimpleResolver()); ++} ++ ++/** ++ * Creates a new Extended Resolver ++ * @param servers An array of server names for which SimpleResolver ++ * contexts should be initialized. ++ * @see SimpleResolver ++ * @exception UnknownHostException Failure occured initializing SimpleResolvers ++ */ ++public ++ExtendedResolver(String [] servers) throws UnknownHostException { ++ init(); ++ for (int i = 0; i < servers.length; i++) { ++ Resolver r = new SimpleResolver(servers[i]); ++ r.setTimeout(quantum); ++ resolvers.add(r); ++ } ++} ++ ++/** ++ * Creates a new Extended Resolver ++ * @param res An array of pre-initialized Resolvers is provided. ++ * @see SimpleResolver ++ * @exception UnknownHostException Failure occured initializing SimpleResolvers ++ */ ++public ++ExtendedResolver(Resolver [] res) throws UnknownHostException { ++ init(); ++ for (int i = 0; i < res.length; i++) ++ resolvers.add(res[i]); ++} ++ ++public void ++setPort(int port) { ++ for (int i = 0; i < resolvers.size(); i++) ++ ((Resolver)resolvers.get(i)).setPort(port); ++} ++ ++public void ++setTCP(boolean flag) { ++ for (int i = 0; i < resolvers.size(); i++) ++ ((Resolver)resolvers.get(i)).setTCP(flag); ++} ++ ++public void ++setIgnoreTruncation(boolean flag) { ++ for (int i = 0; i < resolvers.size(); i++) ++ ((Resolver)resolvers.get(i)).setIgnoreTruncation(flag); ++} ++ ++public void ++setEDNS(int level) { ++ for (int i = 0; i < resolvers.size(); i++) ++ ((Resolver)resolvers.get(i)).setEDNS(level); ++} ++ ++public void ++setEDNS(int level, int payloadSize, int flags, List options) { ++ for (int i = 0; i < resolvers.size(); i++) ++ ((Resolver)resolvers.get(i)).setEDNS(level, payloadSize, ++ flags, options); ++} ++ ++public void ++setTSIGKey(TSIG key) { ++ for (int i = 0; i < resolvers.size(); i++) ++ ((Resolver)resolvers.get(i)).setTSIGKey(key); ++} ++ ++public void ++setTimeout(int secs, int msecs) { ++ for (int i = 0; i < resolvers.size(); i++) ++ ((Resolver)resolvers.get(i)).setTimeout(secs, msecs); ++} ++ ++public void ++setTimeout(int secs) { ++ setTimeout(secs, 0); ++} ++ ++/** ++ * Sends a message and waits for a response. Multiple servers are queried, ++ * and queries are sent multiple times until either a successful response ++ * is received, or it is clear that there is no successful response. ++ * @param query The query to send. ++ * @return The response. ++ * @throws IOException An error occurred while sending or receiving. ++ */ ++public Message ++send(Message query) throws IOException { ++ Resolution res = new Resolution(this, query); ++ return res.start(); ++} ++ ++/** ++ * Asynchronously sends a message to multiple servers, potentially multiple ++ * times, registering a listener to receive a callback on success or exception. ++ * Multiple asynchronous lookups can be performed in parallel. Since the ++ * callback may be invoked before the function returns, external ++ * synchronization is necessary. ++ * @param query The query to send ++ * @param listener The object containing the callbacks. ++ * @return An identifier, which is also a parameter in the callback ++ */ ++public Object ++sendAsync(final Message query, final ResolverListener listener) { ++ Resolution res = new Resolution(this, query); ++ res.startAsync(listener); ++ return res; ++} ++ ++/** Returns the nth resolver used by this ExtendedResolver */ ++public Resolver ++getResolver(int n) { ++ if (n < resolvers.size()) ++ return (Resolver)resolvers.get(n); ++ return null; ++} ++ ++/** Returns all resolvers used by this ExtendedResolver */ ++public Resolver [] ++getResolvers() { ++ return (Resolver []) resolvers.toArray(new Resolver[resolvers.size()]); ++} ++ ++/** Adds a new resolver to be used by this ExtendedResolver */ ++public void ++addResolver(Resolver r) { ++ resolvers.add(r); ++} ++ ++/** Deletes a resolver used by this ExtendedResolver */ ++public void ++deleteResolver(Resolver r) { ++ resolvers.remove(r); ++} ++ ++/** Sets whether the servers should be load balanced. ++ * @param flag If true, servers will be tried in round-robin order. If false, ++ * servers will always be queried in the same order. ++ */ ++public void ++setLoadBalance(boolean flag) { ++ loadBalance = flag; ++} ++ ++/** Sets the number of retries sent to each server per query */ ++public void ++setRetries(int retries) { ++ this.retries = retries; ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Flags.java b/external/asmack/build/src/trunk/org/xbill/DNS/Flags.java +new file mode 100644 +index 0000000..964ce23 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Flags.java +@@ -0,0 +1,81 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Constants and functions relating to flags in the DNS header. ++ * ++ * @author Brian Wellington ++ */ ++ ++public final class Flags { ++ ++private static Mnemonic flags = new Mnemonic("DNS Header Flag", ++ Mnemonic.CASE_LOWER); ++ ++/** query/response */ ++public static final byte QR = 0; ++ ++/** authoritative answer */ ++public static final byte AA = 5; ++ ++/** truncated */ ++public static final byte TC = 6; ++ ++/** recursion desired */ ++public static final byte RD = 7; ++ ++/** recursion available */ ++public static final byte RA = 8; ++ ++/** authenticated data */ ++public static final byte AD = 10; ++ ++/** (security) checking disabled */ ++public static final byte CD = 11; ++ ++/** dnssec ok (extended) */ ++public static final int DO = ExtendedFlags.DO; ++ ++static { ++ flags.setMaximum(0xF); ++ flags.setPrefix("FLAG"); ++ flags.setNumericAllowed(true); ++ ++ flags.add(QR, "qr"); ++ flags.add(AA, "aa"); ++ flags.add(TC, "tc"); ++ flags.add(RD, "rd"); ++ flags.add(RA, "ra"); ++ flags.add(AD, "ad"); ++ flags.add(CD, "cd"); ++} ++ ++private ++Flags() {} ++ ++/** Converts a numeric Flag into a String */ ++public static String ++string(int i) { ++ return flags.getText(i); ++} ++ ++/** Converts a String representation of an Flag into its numeric value */ ++public static int ++value(String s) { ++ return flags.getValue(s); ++} ++ ++/** ++ * Indicates if a bit in the flags field is a flag or not. If it's part of ++ * the rcode or opcode, it's not. ++ */ ++public static boolean ++isFlag(int index) { ++ flags.check(index); ++ if ((index >= 1 && index <= 4) || (index >= 12)) ++ return false; ++ return true; ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/FormattedTime.java b/external/asmack/build/src/trunk/org/xbill/DNS/FormattedTime.java +new file mode 100644 +index 0000000..c76a846 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/FormattedTime.java +@@ -0,0 +1,79 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Routines for converting time values to and from YYYYMMDDHHMMSS format. ++ * ++ * @author Brian Wellington ++ */ ++ ++import java.util.*; ++import java.text.*; ++ ++final class FormattedTime { ++ ++private static NumberFormat w2, w4; ++ ++static { ++ w2 = new DecimalFormat(); ++ w2.setMinimumIntegerDigits(2); ++ ++ w4 = new DecimalFormat(); ++ w4.setMinimumIntegerDigits(4); ++ w4.setGroupingUsed(false); ++} ++ ++private ++FormattedTime() {} ++ ++/** ++ * Converts a Date into a formatted string. ++ * @param date The Date to convert. ++ * @return The formatted string. ++ */ ++public static String ++format(Date date) { ++ Calendar c = new GregorianCalendar(TimeZone.getTimeZone("UTC")); ++ StringBuffer sb = new StringBuffer(); ++ ++ c.setTime(date); ++ sb.append(w4.format(c.get(Calendar.YEAR))); ++ sb.append(w2.format(c.get(Calendar.MONTH)+1)); ++ sb.append(w2.format(c.get(Calendar.DAY_OF_MONTH))); ++ sb.append(w2.format(c.get(Calendar.HOUR_OF_DAY))); ++ sb.append(w2.format(c.get(Calendar.MINUTE))); ++ sb.append(w2.format(c.get(Calendar.SECOND))); ++ return sb.toString(); ++} ++ ++/** ++ * Parses a formatted time string into a Date. ++ * @param s The string, in the form YYYYMMDDHHMMSS. ++ * @return The Date object. ++ * @throws TextParseExcetption The string was invalid. ++ */ ++public static Date ++parse(String s) throws TextParseException { ++ if (s.length() != 14) { ++ throw new TextParseException("Invalid time encoding: " + s); ++ } ++ ++ Calendar c = new GregorianCalendar(TimeZone.getTimeZone("UTC")); ++ c.clear(); ++ try { ++ int year = Integer.parseInt(s.substring(0, 4)); ++ int month = Integer.parseInt(s.substring(4, 6)) - 1; ++ int date = Integer.parseInt(s.substring(6, 8)); ++ int hour = Integer.parseInt(s.substring(8, 10)); ++ int minute = Integer.parseInt(s.substring(10, 12)); ++ int second = Integer.parseInt(s.substring(12, 14)); ++ c.set(year, month, date, hour, minute, second); ++ } ++ catch (NumberFormatException e) { ++ throw new TextParseException("Invalid time encoding: " + s); ++ } ++ return c.getTime(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/GPOSRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/GPOSRecord.java +new file mode 100644 +index 0000000..688d567 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/GPOSRecord.java +@@ -0,0 +1,178 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++/** ++ * Geographical Location - describes the physical location of a host. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class GPOSRecord extends Record { ++ ++private static final long serialVersionUID = -6349714958085750705L; ++ ++private byte [] latitude, longitude, altitude; ++ ++GPOSRecord() {} ++ ++Record ++getObject() { ++ return new GPOSRecord(); ++} ++ ++private void ++validate(double longitude, double latitude) throws IllegalArgumentException ++{ ++ if (longitude < -90.0 || longitude > 90.0) { ++ throw new IllegalArgumentException("illegal longitude " + ++ longitude); ++ } ++ if (latitude < -180.0 || latitude > 180.0) { ++ throw new IllegalArgumentException("illegal latitude " + ++ latitude); ++ } ++} ++ ++/** ++ * Creates an GPOS Record from the given data ++ * @param longitude The longitude component of the location. ++ * @param latitude The latitude component of the location. ++ * @param altitude The altitude component of the location (in meters above sea ++ * level). ++*/ ++public ++GPOSRecord(Name name, int dclass, long ttl, double longitude, double latitude, ++ double altitude) ++{ ++ super(name, Type.GPOS, dclass, ttl); ++ validate(longitude, latitude); ++ this.longitude = Double.toString(longitude).getBytes(); ++ this.latitude = Double.toString(latitude).getBytes(); ++ this.altitude = Double.toString(altitude).getBytes(); ++} ++ ++/** ++ * Creates an GPOS Record from the given data ++ * @param longitude The longitude component of the location. ++ * @param latitude The latitude component of the location. ++ * @param altitude The altitude component of the location (in meters above sea ++ * level). ++*/ ++public ++GPOSRecord(Name name, int dclass, long ttl, String longitude, String latitude, ++ String altitude) ++{ ++ super(name, Type.GPOS, dclass, ttl); ++ try { ++ this.longitude = byteArrayFromString(longitude); ++ this.latitude = byteArrayFromString(latitude); ++ validate(getLongitude(), getLatitude()); ++ this.altitude = byteArrayFromString(altitude); ++ } ++ catch (TextParseException e) { ++ throw new IllegalArgumentException(e.getMessage()); ++ } ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ longitude = in.readCountedString(); ++ latitude = in.readCountedString(); ++ altitude = in.readCountedString(); ++ try { ++ validate(getLongitude(), getLatitude()); ++ } ++ catch(IllegalArgumentException e) { ++ throw new WireParseException(e.getMessage()); ++ } ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ try { ++ longitude = byteArrayFromString(st.getString()); ++ latitude = byteArrayFromString(st.getString()); ++ altitude = byteArrayFromString(st.getString()); ++ } ++ catch (TextParseException e) { ++ throw st.exception(e.getMessage()); ++ } ++ try { ++ validate(getLongitude(), getLatitude()); ++ } ++ catch(IllegalArgumentException e) { ++ throw new WireParseException(e.getMessage()); ++ } ++} ++ ++/** Convert to a String */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(byteArrayToString(longitude, true)); ++ sb.append(" "); ++ sb.append(byteArrayToString(latitude, true)); ++ sb.append(" "); ++ sb.append(byteArrayToString(altitude, true)); ++ return sb.toString(); ++} ++ ++/** Returns the longitude as a string */ ++public String ++getLongitudeString() { ++ return byteArrayToString(longitude, false); ++} ++ ++/** ++ * Returns the longitude as a double ++ * @throws NumberFormatException The string does not contain a valid numeric ++ * value. ++ */ ++public double ++getLongitude() { ++ return Double.parseDouble(getLongitudeString()); ++} ++ ++/** Returns the latitude as a string */ ++public String ++getLatitudeString() { ++ return byteArrayToString(latitude, false); ++} ++ ++/** ++ * Returns the latitude as a double ++ * @throws NumberFormatException The string does not contain a valid numeric ++ * value. ++ */ ++public double ++getLatitude() { ++ return Double.parseDouble(getLatitudeString()); ++} ++ ++/** Returns the altitude as a string */ ++public String ++getAltitudeString() { ++ return byteArrayToString(altitude, false); ++} ++ ++/** ++ * Returns the altitude as a double ++ * @throws NumberFormatException The string does not contain a valid numeric ++ * value. ++ */ ++public double ++getAltitude() { ++ return Double.parseDouble(getAltitudeString()); ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeCountedString(longitude); ++ out.writeCountedString(latitude); ++ out.writeCountedString(altitude); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Generator.java b/external/asmack/build/src/trunk/org/xbill/DNS/Generator.java +new file mode 100644 +index 0000000..a08d343 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Generator.java +@@ -0,0 +1,264 @@ ++// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.util.*; ++ ++/** ++ * A representation of a $GENERATE statement in a master file. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class Generator { ++ ++/** The start of the range. */ ++public long start; ++ ++/** The end of the range. */ ++public long end; ++ ++/** The step value of the range. */ ++public long step; ++ ++/** The pattern to use for generating record names. */ ++public final String namePattern; ++ ++/** The type of the generated records. */ ++public final int type; ++ ++/** The class of the generated records. */ ++public final int dclass; ++ ++/** The ttl of the generated records. */ ++public final long ttl; ++ ++/** The pattern to use for generating record data. */ ++public final String rdataPattern; ++ ++/** The origin to append to relative names. */ ++public final Name origin; ++ ++private long current; ++ ++/** ++ * Indicates whether generation is supported for this type. ++ * @throws InvalidTypeException The type is out of range. ++ */ ++public static boolean ++supportedType(int type) { ++ Type.check(type); ++ return (type == Type.PTR || type == Type.CNAME || type == Type.DNAME || ++ type == Type.A || type == Type.AAAA || type == Type.NS); ++} ++ ++/** ++ * Creates a specification for generating records, as a $GENERATE ++ * statement in a master file. ++ * @param start The start of the range. ++ * @param end The end of the range. ++ * @param step The step value of the range. ++ * @param namePattern The pattern to use for generating record names. ++ * @param type The type of the generated records. The supported types are ++ * PTR, CNAME, DNAME, A, AAAA, and NS. ++ * @param dclass The class of the generated records. ++ * @param ttl The ttl of the generated records. ++ * @param rdataPattern The pattern to use for generating record data. ++ * @param origin The origin to append to relative names. ++ * @throws IllegalArgumentException The range is invalid. ++ * @throws IllegalArgumentException The type does not support generation. ++ * @throws IllegalArgumentException The dclass is not a valid class. ++ */ ++public ++Generator(long start, long end, long step, String namePattern, ++ int type, int dclass, long ttl, String rdataPattern, Name origin) ++{ ++ if (start < 0 || end < 0 || start > end || step <= 0) ++ throw new IllegalArgumentException ++ ("invalid range specification"); ++ if (!supportedType(type)) ++ throw new IllegalArgumentException("unsupported type"); ++ DClass.check(dclass); ++ ++ this.start = start; ++ this.end = end; ++ this.step = step; ++ this.namePattern = namePattern; ++ this.type = type; ++ this.dclass = dclass; ++ this.ttl = ttl; ++ this.rdataPattern = rdataPattern; ++ this.origin = origin; ++ this.current = start; ++} ++ ++private String ++substitute(String spec, long n) throws IOException { ++ boolean escaped = false; ++ byte [] str = spec.getBytes(); ++ StringBuffer sb = new StringBuffer(); ++ ++ for (int i = 0; i < str.length; i++) { ++ char c = (char)(str[i] & 0xFF); ++ if (escaped) { ++ sb.append(c); ++ escaped = false; ++ } else if (c == '\\') { ++ if (i + 1 == str.length) ++ throw new TextParseException ++ ("invalid escape character"); ++ escaped = true; ++ } else if (c == '$') { ++ boolean negative = false; ++ long offset = 0; ++ long width = 0; ++ long base = 10; ++ boolean wantUpperCase = false; ++ if (i + 1 < str.length && str[i + 1] == '$') { ++ // '$$' == literal '$' for backwards ++ // compatibility with old versions of BIND. ++ c = (char)(str[++i] & 0xFF); ++ sb.append(c); ++ continue; ++ } else if (i + 1 < str.length && str[i + 1] == '{') { ++ // It's a substitution with modifiers. ++ i++; ++ if (i + 1 < str.length && str[i + 1] == '-') { ++ negative = true; ++ i++; ++ } ++ while (i + 1 < str.length) { ++ c = (char)(str[++i] & 0xFF); ++ if (c == ',' || c == '}') ++ break; ++ if (c < '0' || c > '9') ++ throw new TextParseException( ++ "invalid offset"); ++ c -= '0'; ++ offset *= 10; ++ offset += c; ++ } ++ if (negative) ++ offset = -offset; ++ ++ if (c == ',') { ++ while (i + 1 < str.length) { ++ c = (char)(str[++i] & 0xFF); ++ if (c == ',' || c == '}') ++ break; ++ if (c < '0' || c > '9') ++ throw new ++ TextParseException( ++ "invalid width"); ++ c -= '0'; ++ width *= 10; ++ width += c; ++ } ++ } ++ ++ if (c == ',') { ++ if (i + 1 == str.length) ++ throw new TextParseException( ++ "invalid base"); ++ c = (char)(str[++i] & 0xFF); ++ if (c == 'o') ++ base = 8; ++ else if (c == 'x') ++ base = 16; ++ else if (c == 'X') { ++ base = 16; ++ wantUpperCase = true; ++ } ++ else if (c != 'd') ++ throw new TextParseException( ++ "invalid base"); ++ } ++ ++ if (i + 1 == str.length || str[i + 1] != '}') ++ throw new TextParseException ++ ("invalid modifiers"); ++ i++; ++ } ++ long v = n + offset; ++ if (v < 0) ++ throw new TextParseException ++ ("invalid offset expansion"); ++ String number; ++ if (base == 8) ++ number = Long.toOctalString(v); ++ else if (base == 16) ++ number = Long.toHexString(v); ++ else ++ number = Long.toString(v); ++ if (wantUpperCase) ++ number = number.toUpperCase(); ++ if (width != 0 && width > number.length()) { ++ int zeros = (int)width - number.length(); ++ while (zeros-- > 0) ++ sb.append('0'); ++ } ++ sb.append(number); ++ } else { ++ sb.append(c); ++ } ++ } ++ return sb.toString(); ++} ++ ++/** ++ * Constructs and returns the next record in the expansion. ++ * @throws IOException The name or rdata was invalid after substitutions were ++ * performed. ++ */ ++public Record ++nextRecord() throws IOException { ++ if (current > end) ++ return null; ++ String namestr = substitute(namePattern, current); ++ Name name = Name.fromString(namestr, origin); ++ String rdata = substitute(rdataPattern, current); ++ current += step; ++ return Record.fromString(name, type, dclass, ttl, rdata, origin); ++} ++ ++/** ++ * Constructs and returns all records in the expansion. ++ * @throws IOException The name or rdata of a record was invalid after ++ * substitutions were performed. ++ */ ++public Record [] ++expand() throws IOException { ++ List list = new ArrayList(); ++ for (long i = start; i < end; i += step) { ++ String namestr = substitute(namePattern, current); ++ Name name = Name.fromString(namestr, origin); ++ String rdata = substitute(rdataPattern, current); ++ list.add(Record.fromString(name, type, dclass, ttl, ++ rdata, origin)); ++ } ++ return (Record []) list.toArray(new Record[list.size()]); ++} ++ ++/** ++ * Converts the generate specification to a string containing the corresponding ++ * $GENERATE statement. ++ */ ++public String ++toString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append("$GENERATE "); ++ sb.append(start + "-" + end); ++ if (step > 1) ++ sb.append("/" + step); ++ sb.append(" "); ++ sb.append(namePattern + " "); ++ sb.append(ttl + " "); ++ if (dclass != DClass.IN || !Options.check("noPrintIN")) ++ sb.append(DClass.string(dclass) + " "); ++ sb.append(Type.string(type) + " "); ++ sb.append(rdataPattern + " "); ++ return sb.toString(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/GenericEDNSOption.java b/external/asmack/build/src/trunk/org/xbill/DNS/GenericEDNSOption.java +new file mode 100644 +index 0000000..7c8b51b +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/GenericEDNSOption.java +@@ -0,0 +1,47 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++import org.xbill.DNS.utils.base16; ++ ++/** ++ * An EDNSOption with no internal structure. ++ * ++ * @author Ming Zhou <mizhou@bnivideo.com>, Beaumaris Networks ++ * @author Brian Wellington ++ */ ++public class GenericEDNSOption extends EDNSOption { ++ ++private byte [] data; ++ ++GenericEDNSOption(int code) { ++ super(code); ++} ++ ++/** ++ * Construct a generic EDNS option. ++ * @param data The contents of the option. ++ */ ++public ++GenericEDNSOption(int code, byte [] data) { ++ super(code); ++ this.data = Record.checkByteArrayLength("option data", data, 0xFFFF); ++} ++ ++void ++optionFromWire(DNSInput in) throws IOException { ++ data = in.readByteArray(); ++} ++ ++void ++optionToWire(DNSOutput out) { ++ out.writeByteArray(data); ++} ++ ++String ++optionToString() { ++ return "<" + base16.toString(data) + ">"; ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/HINFORecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/HINFORecord.java +new file mode 100644 +index 0000000..18fed32 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/HINFORecord.java +@@ -0,0 +1,95 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++/** ++ * Host Information - describes the CPU and OS of a host ++ * ++ * @author Brian Wellington ++ */ ++ ++public class HINFORecord extends Record { ++ ++private static final long serialVersionUID = -4732870630947452112L; ++ ++private byte [] cpu, os; ++ ++HINFORecord() {} ++ ++Record ++getObject() { ++ return new HINFORecord(); ++} ++ ++/** ++ * Creates an HINFO Record from the given data ++ * @param cpu A string describing the host's CPU ++ * @param os A string describing the host's OS ++ * @throws IllegalArgumentException One of the strings has invalid escapes ++ */ ++public ++HINFORecord(Name name, int dclass, long ttl, String cpu, String os) { ++ super(name, Type.HINFO, dclass, ttl); ++ try { ++ this.cpu = byteArrayFromString(cpu); ++ this.os = byteArrayFromString(os); ++ } ++ catch (TextParseException e) { ++ throw new IllegalArgumentException(e.getMessage()); ++ } ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ cpu = in.readCountedString(); ++ os = in.readCountedString(); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ try { ++ cpu = byteArrayFromString(st.getString()); ++ os = byteArrayFromString(st.getString()); ++ } ++ catch (TextParseException e) { ++ throw st.exception(e.getMessage()); ++ } ++} ++ ++/** ++ * Returns the host's CPU ++ */ ++public String ++getCPU() { ++ return byteArrayToString(cpu, false); ++} ++ ++/** ++ * Returns the host's OS ++ */ ++public String ++getOS() { ++ return byteArrayToString(os, false); ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeCountedString(cpu); ++ out.writeCountedString(os); ++} ++ ++/** ++ * Converts to a string ++ */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(byteArrayToString(cpu, true)); ++ sb.append(" "); ++ sb.append(byteArrayToString(os, true)); ++ return sb.toString(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Header.java b/external/asmack/build/src/trunk/org/xbill/DNS/Header.java +new file mode 100644 +index 0000000..2a44d08 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Header.java +@@ -0,0 +1,286 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.util.*; ++ ++/** ++ * A DNS message header ++ * @see Message ++ * ++ * @author Brian Wellington ++ */ ++ ++public class Header implements Cloneable { ++ ++private int id; ++private int flags; ++private int [] counts; ++ ++private static Random random = new Random(); ++ ++/** The length of a DNS Header in wire format. */ ++public static final int LENGTH = 12; ++ ++private void ++init() { ++ counts = new int[4]; ++ flags = 0; ++ id = -1; ++} ++ ++/** ++ * Create a new empty header. ++ * @param id The message id ++ */ ++public ++Header(int id) { ++ init(); ++ setID(id); ++} ++ ++/** ++ * Create a new empty header with a random message id ++ */ ++public ++Header() { ++ init(); ++} ++ ++/** ++ * Parses a Header from a stream containing DNS wire format. ++ */ ++Header(DNSInput in) throws IOException { ++ this(in.readU16()); ++ flags = in.readU16(); ++ for (int i = 0; i < counts.length; i++) ++ counts[i] = in.readU16(); ++} ++ ++/** ++ * Creates a new Header from its DNS wire format representation ++ * @param b A byte array containing the DNS Header. ++ */ ++public ++Header(byte [] b) throws IOException { ++ this(new DNSInput(b)); ++} ++ ++void ++toWire(DNSOutput out) { ++ out.writeU16(getID()); ++ out.writeU16(flags); ++ for (int i = 0; i < counts.length; i++) ++ out.writeU16(counts[i]); ++} ++ ++public byte [] ++toWire() { ++ DNSOutput out = new DNSOutput(); ++ toWire(out); ++ return out.toByteArray(); ++} ++ ++static private boolean ++validFlag(int bit) { ++ return (bit >= 0 && bit <= 0xF && Flags.isFlag(bit)); ++} ++ ++static private void ++checkFlag(int bit) { ++ if (!validFlag(bit)) ++ throw new IllegalArgumentException("invalid flag bit " + bit); ++} ++ ++/** ++ * Sets a flag to the supplied value ++ * @see Flags ++ */ ++public void ++setFlag(int bit) { ++ checkFlag(bit); ++ // bits are indexed from left to right ++ flags |= (1 << (15 - bit)); ++} ++ ++/** ++ * Sets a flag to the supplied value ++ * @see Flags ++ */ ++public void ++unsetFlag(int bit) { ++ checkFlag(bit); ++ // bits are indexed from left to right ++ flags &= ~(1 << (15 - bit)); ++} ++ ++/** ++ * Retrieves a flag ++ * @see Flags ++ */ ++public boolean ++getFlag(int bit) { ++ checkFlag(bit); ++ // bits are indexed from left to right ++ return (flags & (1 << (15 - bit))) != 0; ++} ++ ++boolean [] ++getFlags() { ++ boolean [] array = new boolean[16]; ++ for (int i = 0; i < array.length; i++) ++ if (validFlag(i)) ++ array[i] = getFlag(i); ++ return array; ++} ++ ++/** ++ * Retrieves the message ID ++ */ ++public int ++getID() { ++ if (id >= 0) ++ return id; ++ synchronized (this) { ++ if (id < 0) ++ id = random.nextInt(0xffff); ++ return id; ++ } ++} ++ ++/** ++ * Sets the message ID ++ */ ++public void ++setID(int id) { ++ if (id < 0 || id > 0xffff) ++ throw new IllegalArgumentException("DNS message ID " + id + ++ " is out of range"); ++ this.id = id; ++} ++ ++/** ++ * Sets the message's rcode ++ * @see Rcode ++ */ ++public void ++setRcode(int value) { ++ if (value < 0 || value > 0xF) ++ throw new IllegalArgumentException("DNS Rcode " + value + ++ " is out of range"); ++ flags &= ~0xF; ++ flags |= value; ++} ++ ++/** ++ * Retrieves the mesasge's rcode ++ * @see Rcode ++ */ ++public int ++getRcode() { ++ return flags & 0xF; ++} ++ ++/** ++ * Sets the message's opcode ++ * @see Opcode ++ */ ++public void ++setOpcode(int value) { ++ if (value < 0 || value > 0xF) ++ throw new IllegalArgumentException("DNS Opcode " + value + ++ "is out of range"); ++ flags &= 0x87FF; ++ flags |= (value << 11); ++} ++ ++/** ++ * Retrieves the mesasge's opcode ++ * @see Opcode ++ */ ++public int ++getOpcode() { ++ return (flags >> 11) & 0xF; ++} ++ ++void ++setCount(int field, int value) { ++ if (value < 0 || value > 0xFFFF) ++ throw new IllegalArgumentException("DNS section count " + ++ value + " is out of range"); ++ counts[field] = value; ++} ++ ++void ++incCount(int field) { ++ if (counts[field] == 0xFFFF) ++ throw new IllegalStateException("DNS section count cannot " + ++ "be incremented"); ++ counts[field]++; ++} ++ ++void ++decCount(int field) { ++ if (counts[field] == 0) ++ throw new IllegalStateException("DNS section count cannot " + ++ "be decremented"); ++ counts[field]--; ++} ++ ++/** ++ * Retrieves the record count for the given section ++ * @see Section ++ */ ++public int ++getCount(int field) { ++ return counts[field]; ++} ++ ++/** Converts the header's flags into a String */ ++public String ++printFlags() { ++ StringBuffer sb = new StringBuffer(); ++ ++ for (int i = 0; i < 16; i++) ++ if (validFlag(i) && getFlag(i)) { ++ sb.append(Flags.string(i)); ++ sb.append(" "); ++ } ++ return sb.toString(); ++} ++ ++String ++toStringWithRcode(int newrcode) { ++ StringBuffer sb = new StringBuffer(); ++ ++ sb.append(";; ->>HEADER<<- "); ++ sb.append("opcode: " + Opcode.string(getOpcode())); ++ sb.append(", status: " + Rcode.string(newrcode)); ++ sb.append(", id: " + getID()); ++ sb.append("\n"); ++ ++ sb.append(";; flags: " + printFlags()); ++ sb.append("; "); ++ for (int i = 0; i < 4; i++) ++ sb.append(Section.string(i) + ": " + getCount(i) + " "); ++ return sb.toString(); ++} ++ ++/** Converts the header into a String */ ++public String ++toString() { ++ return toStringWithRcode(getRcode()); ++} ++ ++/* Creates a new Header identical to the current one */ ++public Object ++clone() { ++ Header h = new Header(); ++ h.id = id; ++ h.flags = flags; ++ System.arraycopy(counts, 0, h.counts, 0, counts.length); ++ return h; ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/IPSECKEYRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/IPSECKEYRecord.java +new file mode 100644 +index 0000000..7eb2956 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/IPSECKEYRecord.java +@@ -0,0 +1,231 @@ ++// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.net.*; ++import org.xbill.DNS.utils.*; ++ ++/** ++ * IPsec Keying Material (RFC 4025) ++ * ++ * @author Brian Wellington ++ */ ++ ++public class IPSECKEYRecord extends Record { ++ ++private static final long serialVersionUID = 3050449702765909687L; ++ ++public static class Algorithm { ++ private Algorithm() {} ++ ++ public static final int DSA = 1; ++ public static final int RSA = 2; ++} ++ ++public static class Gateway { ++ private Gateway() {} ++ ++ public static final int None = 0; ++ public static final int IPv4 = 1; ++ public static final int IPv6 = 2; ++ public static final int Name = 3; ++} ++ ++private int precedence; ++private int gatewayType; ++private int algorithmType; ++private Object gateway; ++private byte [] key; ++ ++IPSECKEYRecord() {} ++ ++Record ++getObject() { ++ return new IPSECKEYRecord(); ++} ++ ++/** ++ * Creates an IPSECKEY Record from the given data. ++ * @param precedence The record's precedence. ++ * @param gatewayType The record's gateway type. ++ * @param algorithmType The record's algorithm type. ++ * @param gateway The record's gateway. ++ * @param key The record's public key. ++ */ ++public ++IPSECKEYRecord(Name name, int dclass, long ttl, int precedence, ++ int gatewayType, int algorithmType, Object gateway, ++ byte [] key) ++{ ++ super(name, Type.IPSECKEY, dclass, ttl); ++ this.precedence = checkU8("precedence", precedence); ++ this.gatewayType = checkU8("gatewayType", gatewayType); ++ this.algorithmType = checkU8("algorithmType", algorithmType); ++ switch (gatewayType) { ++ case Gateway.None: ++ this.gateway = null; ++ break; ++ case Gateway.IPv4: ++ if (!(gateway instanceof InetAddress)) ++ throw new IllegalArgumentException("\"gateway\" " + ++ "must be an IPv4 " + ++ "address"); ++ this.gateway = gateway; ++ break; ++ case Gateway.IPv6: ++ if (!(gateway instanceof Inet6Address)) ++ throw new IllegalArgumentException("\"gateway\" " + ++ "must be an IPv6 " + ++ "address"); ++ this.gateway = gateway; ++ break; ++ case Gateway.Name: ++ if (!(gateway instanceof Name)) ++ throw new IllegalArgumentException("\"gateway\" " + ++ "must be a DNS " + ++ "name"); ++ this.gateway = checkName("gateway", (Name) gateway); ++ break; ++ default: ++ throw new IllegalArgumentException("\"gatewayType\" " + ++ "must be between 0 and 3"); ++ } ++ ++ this.key = key; ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ precedence = in.readU8(); ++ gatewayType = in.readU8(); ++ algorithmType = in.readU8(); ++ switch (gatewayType) { ++ case Gateway.None: ++ gateway = null; ++ break; ++ case Gateway.IPv4: ++ gateway = InetAddress.getByAddress(in.readByteArray(4)); ++ break; ++ case Gateway.IPv6: ++ gateway = InetAddress.getByAddress(in.readByteArray(16)); ++ break; ++ case Gateway.Name: ++ gateway = new Name(in); ++ break; ++ default: ++ throw new WireParseException("invalid gateway type"); ++ } ++ if (in.remaining() > 0) ++ key = in.readByteArray(); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ precedence = st.getUInt8(); ++ gatewayType = st.getUInt8(); ++ algorithmType = st.getUInt8(); ++ switch (gatewayType) { ++ case Gateway.None: ++ String s = st.getString(); ++ if (!s.equals(".")) ++ throw new TextParseException("invalid gateway format"); ++ gateway = null; ++ break; ++ case Gateway.IPv4: ++ gateway = st.getAddress(Address.IPv4); ++ break; ++ case Gateway.IPv6: ++ gateway = st.getAddress(Address.IPv6); ++ break; ++ case Gateway.Name: ++ gateway = st.getName(origin); ++ break; ++ default: ++ throw new WireParseException("invalid gateway type"); ++ } ++ key = st.getBase64(false); ++} ++ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(precedence); ++ sb.append(" "); ++ sb.append(gatewayType); ++ sb.append(" "); ++ sb.append(algorithmType); ++ sb.append(" "); ++ switch (gatewayType) { ++ case Gateway.None: ++ sb.append("."); ++ break; ++ case Gateway.IPv4: ++ case Gateway.IPv6: ++ InetAddress gatewayAddr = (InetAddress) gateway; ++ sb.append(gatewayAddr.getHostAddress()); ++ break; ++ case Gateway.Name: ++ sb.append(gateway); ++ break; ++ } ++ if (key != null) { ++ sb.append(" "); ++ sb.append(base64.toString(key)); ++ } ++ return sb.toString(); ++} ++ ++/** Returns the record's precedence. */ ++public int ++getPrecedence() { ++ return precedence; ++} ++ ++/** Returns the record's gateway type. */ ++public int ++getGatewayType() { ++ return gatewayType; ++} ++ ++/** Returns the record's algorithm type. */ ++public int ++getAlgorithmType() { ++ return algorithmType; ++} ++ ++/** Returns the record's gateway. */ ++public Object ++getGateway() { ++ return gateway; ++} ++ ++/** Returns the record's public key */ ++public byte [] ++getKey() { ++ return key; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeU8(precedence); ++ out.writeU8(gatewayType); ++ out.writeU8(algorithmType); ++ switch (gatewayType) { ++ case Gateway.None: ++ break; ++ case Gateway.IPv4: ++ case Gateway.IPv6: ++ InetAddress gatewayAddr = (InetAddress) gateway; ++ out.writeByteArray(gatewayAddr.getAddress()); ++ break; ++ case Gateway.Name: ++ Name gatewayName = (Name) gateway; ++ gatewayName.toWire(out, null, canonical); ++ break; ++ } ++ if (key != null) ++ out.writeByteArray(key); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ISDNRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/ISDNRecord.java +new file mode 100644 +index 0000000..8f9b629 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/ISDNRecord.java +@@ -0,0 +1,105 @@ ++// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++/** ++ * ISDN - identifies the ISDN number and subaddress associated with a name. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class ISDNRecord extends Record { ++ ++private static final long serialVersionUID = -8730801385178968798L; ++ ++private byte [] address; ++private byte [] subAddress; ++ ++ISDNRecord() {} ++ ++Record ++getObject() { ++ return new ISDNRecord(); ++} ++ ++/** ++ * Creates an ISDN Record from the given data ++ * @param address The ISDN number associated with the domain. ++ * @param subAddress The subaddress, if any. ++ * @throws IllegalArgumentException One of the strings is invalid. ++ */ ++public ++ISDNRecord(Name name, int dclass, long ttl, String address, String subAddress) { ++ super(name, Type.ISDN, dclass, ttl); ++ try { ++ this.address = byteArrayFromString(address); ++ if (subAddress != null) ++ this.subAddress = byteArrayFromString(subAddress); ++ } ++ catch (TextParseException e) { ++ throw new IllegalArgumentException(e.getMessage()); ++ } ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ address = in.readCountedString(); ++ if (in.remaining() > 0) ++ subAddress = in.readCountedString(); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ try { ++ address = byteArrayFromString(st.getString()); ++ Tokenizer.Token t = st.get(); ++ if (t.isString()) { ++ subAddress = byteArrayFromString(t.value); ++ } else { ++ st.unget(); ++ } ++ } ++ catch (TextParseException e) { ++ throw st.exception(e.getMessage()); ++ } ++} ++ ++/** ++ * Returns the ISDN number associated with the domain. ++ */ ++public String ++getAddress() { ++ return byteArrayToString(address, false); ++} ++ ++/** ++ * Returns the ISDN subaddress, or null if there is none. ++ */ ++public String ++getSubAddress() { ++ if (subAddress == null) ++ return null; ++ return byteArrayToString(subAddress, false); ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeCountedString(address); ++ if (subAddress != null) ++ out.writeCountedString(subAddress); ++} ++ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(byteArrayToString(address, true)); ++ if (subAddress != null) { ++ sb.append(" "); ++ sb.append(byteArrayToString(subAddress, true)); ++ } ++ return sb.toString(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/InvalidDClassException.java b/external/asmack/build/src/trunk/org/xbill/DNS/InvalidDClassException.java +new file mode 100644 +index 0000000..6c95cd4 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/InvalidDClassException.java +@@ -0,0 +1,18 @@ ++// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * An exception thrown when an invalid dclass code is specified. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class InvalidDClassException extends IllegalArgumentException { ++ ++public ++InvalidDClassException(int dclass) { ++ super("Invalid DNS class: " + dclass); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/InvalidTTLException.java b/external/asmack/build/src/trunk/org/xbill/DNS/InvalidTTLException.java +new file mode 100644 +index 0000000..95776fe +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/InvalidTTLException.java +@@ -0,0 +1,18 @@ ++// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * An exception thrown when an invalid TTL is specified. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class InvalidTTLException extends IllegalArgumentException { ++ ++public ++InvalidTTLException(long ttl) { ++ super("Invalid DNS TTL: " + ttl); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/InvalidTypeException.java b/external/asmack/build/src/trunk/org/xbill/DNS/InvalidTypeException.java +new file mode 100644 +index 0000000..7c61276 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/InvalidTypeException.java +@@ -0,0 +1,18 @@ ++// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * An exception thrown when an invalid type code is specified. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class InvalidTypeException extends IllegalArgumentException { ++ ++public ++InvalidTypeException(int type) { ++ super("Invalid DNS type: " + type); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/KEYBase.java b/external/asmack/build/src/trunk/org/xbill/DNS/KEYBase.java +new file mode 100644 +index 0000000..59a2c6c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/KEYBase.java +@@ -0,0 +1,161 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.security.PublicKey; ++ ++import org.xbill.DNS.utils.*; ++ ++/** ++ * The base class for KEY/DNSKEY records, which have identical formats ++ * ++ * @author Brian Wellington ++ */ ++ ++abstract class KEYBase extends Record { ++ ++private static final long serialVersionUID = 3469321722693285454L; ++ ++protected int flags, proto, alg; ++protected byte [] key; ++protected int footprint = -1; ++protected PublicKey publicKey = null; ++ ++protected ++KEYBase() {} ++ ++public ++KEYBase(Name name, int type, int dclass, long ttl, int flags, int proto, ++ int alg, byte [] key) ++{ ++ super(name, type, dclass, ttl); ++ this.flags = checkU16("flags", flags); ++ this.proto = checkU8("proto", proto); ++ this.alg = checkU8("alg", alg); ++ this.key = key; ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ flags = in.readU16(); ++ proto = in.readU8(); ++ alg = in.readU8(); ++ if (in.remaining() > 0) ++ key = in.readByteArray(); ++} ++ ++/** Converts the DNSKEY/KEY Record to a String */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(flags); ++ sb.append(" "); ++ sb.append(proto); ++ sb.append(" "); ++ sb.append(alg); ++ if (key != null) { ++ if (Options.check("multiline")) { ++ sb.append(" (\n"); ++ sb.append(base64.formatString(key, 64, "\t", true)); ++ sb.append(" ; key_tag = "); ++ sb.append(getFootprint()); ++ } else { ++ sb.append(" "); ++ sb.append(base64.toString(key)); ++ } ++ } ++ return sb.toString(); ++} ++ ++/** ++ * Returns the flags describing the key's properties ++ */ ++public int ++getFlags() { ++ return flags; ++} ++ ++/** ++ * Returns the protocol that the key was created for ++ */ ++public int ++getProtocol() { ++ return proto; ++} ++ ++/** ++ * Returns the key's algorithm ++ */ ++public int ++getAlgorithm() { ++ return alg; ++} ++ ++/** ++ * Returns the binary data representing the key ++ */ ++public byte [] ++getKey() { ++ return key; ++} ++ ++/** ++ * Returns the key's footprint (after computing it) ++ */ ++public int ++getFootprint() { ++ if (footprint >= 0) ++ return footprint; ++ ++ int foot = 0; ++ ++ DNSOutput out = new DNSOutput(); ++ rrToWire(out, null, false); ++ byte [] rdata = out.toByteArray(); ++ ++ if (alg == DNSSEC.Algorithm.RSAMD5) { ++ int d1 = rdata[rdata.length - 3] & 0xFF; ++ int d2 = rdata[rdata.length - 2] & 0xFF; ++ foot = (d1 << 8) + d2; ++ } ++ else { ++ int i; ++ for (i = 0; i < rdata.length - 1; i += 2) { ++ int d1 = rdata[i] & 0xFF; ++ int d2 = rdata[i + 1] & 0xFF; ++ foot += ((d1 << 8) + d2); ++ } ++ if (i < rdata.length) { ++ int d1 = rdata[i] & 0xFF; ++ foot += (d1 << 8); ++ } ++ foot += ((foot >> 16) & 0xFFFF); ++ } ++ footprint = (foot & 0xFFFF); ++ return footprint; ++} ++ ++/** ++ * Returns a PublicKey corresponding to the data in this key. ++ * @throws DNSSEC.DNSSECException The key could not be converted. ++ */ ++public PublicKey ++getPublicKey() throws DNSSEC.DNSSECException { ++ if (publicKey != null) ++ return publicKey; ++ ++ publicKey = DNSSEC.toPublicKey(this); ++ return publicKey; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeU16(flags); ++ out.writeU8(proto); ++ out.writeU8(alg); ++ if (key != null) ++ out.writeByteArray(key); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/KEYRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/KEYRecord.java +new file mode 100644 +index 0000000..3d2e01c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/KEYRecord.java +@@ -0,0 +1,352 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.security.PublicKey; ++import java.util.*; ++ ++/** ++ * Key - contains a cryptographic public key. The data can be converted ++ * to objects implementing java.security.interfaces.PublicKey ++ * @see DNSSEC ++ * ++ * @author Brian Wellington ++ */ ++ ++public class KEYRecord extends KEYBase { ++ ++private static final long serialVersionUID = 6385613447571488906L; ++ ++public static class Protocol { ++ /** ++ * KEY protocol identifiers. ++ */ ++ ++ private Protocol() {} ++ ++ /** No defined protocol. */ ++ public static final int NONE = 0; ++ ++ /** Transaction Level Security */ ++ public static final int TLS = 1; ++ ++ /** Email */ ++ public static final int EMAIL = 2; ++ ++ /** DNSSEC */ ++ public static final int DNSSEC = 3; ++ ++ /** IPSEC Control */ ++ public static final int IPSEC = 4; ++ ++ /** Any protocol */ ++ public static final int ANY = 255; ++ ++ private static Mnemonic protocols = new Mnemonic("KEY protocol", ++ Mnemonic.CASE_UPPER); ++ ++ static { ++ protocols.setMaximum(0xFF); ++ protocols.setNumericAllowed(true); ++ ++ protocols.add(NONE, "NONE"); ++ protocols.add(TLS, "TLS"); ++ protocols.add(EMAIL, "EMAIL"); ++ protocols.add(DNSSEC, "DNSSEC"); ++ protocols.add(IPSEC, "IPSEC"); ++ protocols.add(ANY, "ANY"); ++ } ++ ++ /** ++ * Converts an KEY protocol value into its textual representation ++ */ ++ public static String ++ string(int type) { ++ return protocols.getText(type); ++ } ++ ++ /** ++ * Converts a textual representation of a KEY protocol into its ++ * numeric code. Integers in the range 0..255 are also accepted. ++ * @param s The textual representation of the protocol ++ * @return The protocol code, or -1 on error. ++ */ ++ public static int ++ value(String s) { ++ return protocols.getValue(s); ++ } ++} ++ ++public static class Flags { ++ /** ++ * KEY flags identifiers. ++ */ ++ ++ private Flags() {} ++ ++ /** KEY cannot be used for confidentiality */ ++ public static final int NOCONF = 0x4000; ++ ++ /** KEY cannot be used for authentication */ ++ public static final int NOAUTH = 0x8000; ++ ++ /** No key present */ ++ public static final int NOKEY = 0xC000; ++ ++ /** Bitmask of the use fields */ ++ public static final int USE_MASK = 0xC000; ++ ++ /** Flag 2 (unused) */ ++ public static final int FLAG2 = 0x2000; ++ ++ /** Flags extension */ ++ public static final int EXTEND = 0x1000; ++ ++ /** Flag 4 (unused) */ ++ public static final int FLAG4 = 0x0800; ++ ++ /** Flag 5 (unused) */ ++ public static final int FLAG5 = 0x0400; ++ ++ /** Key is owned by a user. */ ++ public static final int USER = 0x0000; ++ ++ /** Key is owned by a zone. */ ++ public static final int ZONE = 0x0100; ++ ++ /** Key is owned by a host. */ ++ public static final int HOST = 0x0200; ++ ++ /** Key owner type 3 (reserved). */ ++ public static final int NTYP3 = 0x0300; ++ ++ /** Key owner bitmask. */ ++ public static final int OWNER_MASK = 0x0300; ++ ++ /** Flag 8 (unused) */ ++ public static final int FLAG8 = 0x0080; ++ ++ /** Flag 9 (unused) */ ++ public static final int FLAG9 = 0x0040; ++ ++ /** Flag 10 (unused) */ ++ public static final int FLAG10 = 0x0020; ++ ++ /** Flag 11 (unused) */ ++ public static final int FLAG11 = 0x0010; ++ ++ /** Signatory value 0 */ ++ public static final int SIG0 = 0; ++ ++ /** Signatory value 1 */ ++ public static final int SIG1 = 1; ++ ++ /** Signatory value 2 */ ++ public static final int SIG2 = 2; ++ ++ /** Signatory value 3 */ ++ public static final int SIG3 = 3; ++ ++ /** Signatory value 4 */ ++ public static final int SIG4 = 4; ++ ++ /** Signatory value 5 */ ++ public static final int SIG5 = 5; ++ ++ /** Signatory value 6 */ ++ public static final int SIG6 = 6; ++ ++ /** Signatory value 7 */ ++ public static final int SIG7 = 7; ++ ++ /** Signatory value 8 */ ++ public static final int SIG8 = 8; ++ ++ /** Signatory value 9 */ ++ public static final int SIG9 = 9; ++ ++ /** Signatory value 10 */ ++ public static final int SIG10 = 10; ++ ++ /** Signatory value 11 */ ++ public static final int SIG11 = 11; ++ ++ /** Signatory value 12 */ ++ public static final int SIG12 = 12; ++ ++ /** Signatory value 13 */ ++ public static final int SIG13 = 13; ++ ++ /** Signatory value 14 */ ++ public static final int SIG14 = 14; ++ ++ /** Signatory value 15 */ ++ public static final int SIG15 = 15; ++ ++ private static Mnemonic flags = new Mnemonic("KEY flags", ++ Mnemonic.CASE_UPPER); ++ ++ static { ++ flags.setMaximum(0xFFFF); ++ flags.setNumericAllowed(false); ++ ++ flags.add(NOCONF, "NOCONF"); ++ flags.add(NOAUTH, "NOAUTH"); ++ flags.add(NOKEY, "NOKEY"); ++ flags.add(FLAG2, "FLAG2"); ++ flags.add(EXTEND, "EXTEND"); ++ flags.add(FLAG4, "FLAG4"); ++ flags.add(FLAG5, "FLAG5"); ++ flags.add(USER, "USER"); ++ flags.add(ZONE, "ZONE"); ++ flags.add(HOST, "HOST"); ++ flags.add(NTYP3, "NTYP3"); ++ flags.add(FLAG8, "FLAG8"); ++ flags.add(FLAG9, "FLAG9"); ++ flags.add(FLAG10, "FLAG10"); ++ flags.add(FLAG11, "FLAG11"); ++ flags.add(SIG0, "SIG0"); ++ flags.add(SIG1, "SIG1"); ++ flags.add(SIG2, "SIG2"); ++ flags.add(SIG3, "SIG3"); ++ flags.add(SIG4, "SIG4"); ++ flags.add(SIG5, "SIG5"); ++ flags.add(SIG6, "SIG6"); ++ flags.add(SIG7, "SIG7"); ++ flags.add(SIG8, "SIG8"); ++ flags.add(SIG9, "SIG9"); ++ flags.add(SIG10, "SIG10"); ++ flags.add(SIG11, "SIG11"); ++ flags.add(SIG12, "SIG12"); ++ flags.add(SIG13, "SIG13"); ++ flags.add(SIG14, "SIG14"); ++ flags.add(SIG15, "SIG15"); ++ } ++ ++ /** ++ * Converts a textual representation of KEY flags into its ++ * numeric code. Integers in the range 0..65535 are also accepted. ++ * @param s The textual representation of the protocol ++ * @return The protocol code, or -1 on error. ++ */ ++ public static int ++ value(String s) { ++ int value; ++ try { ++ value = Integer.parseInt(s); ++ if (value >= 0 && value <= 0xFFFF) { ++ return value; ++ } ++ return -1; ++ } catch (NumberFormatException e) { ++ } ++ StringTokenizer st = new StringTokenizer(s, "|"); ++ value = 0; ++ while (st.hasMoreTokens()) { ++ int val = flags.getValue(st.nextToken()); ++ if (val < 0) { ++ return -1; ++ } ++ value |= val; ++ } ++ return value; ++ } ++} ++ ++/* flags */ ++/** This key cannot be used for confidentiality (encryption) */ ++public static final int FLAG_NOCONF = Flags.NOCONF; ++ ++/** This key cannot be used for authentication */ ++public static final int FLAG_NOAUTH = Flags.NOAUTH; ++ ++/** This key cannot be used for authentication or confidentiality */ ++public static final int FLAG_NOKEY = Flags.NOKEY; ++ ++/** A zone key */ ++public static final int OWNER_ZONE = Flags.ZONE; ++ ++/** A host/end entity key */ ++public static final int OWNER_HOST = Flags.HOST; ++ ++/** A user key */ ++public static final int OWNER_USER = Flags.USER; ++ ++/* protocols */ ++/** Key was created for use with transaction level security */ ++public static final int PROTOCOL_TLS = Protocol.TLS; ++ ++/** Key was created for use with email */ ++public static final int PROTOCOL_EMAIL = Protocol.EMAIL; ++ ++/** Key was created for use with DNSSEC */ ++public static final int PROTOCOL_DNSSEC = Protocol.DNSSEC; ++ ++/** Key was created for use with IPSEC */ ++public static final int PROTOCOL_IPSEC = Protocol.IPSEC; ++ ++/** Key was created for use with any protocol */ ++public static final int PROTOCOL_ANY = Protocol.ANY; ++ ++KEYRecord() {} ++ ++Record ++getObject() { ++ return new KEYRecord(); ++} ++ ++/** ++ * Creates a KEY Record from the given data ++ * @param flags Flags describing the key's properties ++ * @param proto The protocol that the key was created for ++ * @param alg The key's algorithm ++ * @param key Binary data representing the key ++ */ ++public ++KEYRecord(Name name, int dclass, long ttl, int flags, int proto, int alg, ++ byte [] key) ++{ ++ super(name, Type.KEY, dclass, ttl, flags, proto, alg, key); ++} ++ ++/** ++ * Creates a KEY Record from the given data ++ * @param flags Flags describing the key's properties ++ * @param proto The protocol that the key was created for ++ * @param alg The key's algorithm ++ * @param key The key as a PublicKey ++ * @throws DNSSEC.DNSSECException The PublicKey could not be converted into DNS ++ * format. ++ */ ++public ++KEYRecord(Name name, int dclass, long ttl, int flags, int proto, int alg, ++ PublicKey key) throws DNSSEC.DNSSECException ++{ ++ super(name, Type.KEY, dclass, ttl, flags, proto, alg, ++ DNSSEC.fromPublicKey(key, alg)); ++ publicKey = key; ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ String flagString = st.getIdentifier(); ++ flags = Flags.value(flagString); ++ if (flags < 0) ++ throw st.exception("Invalid flags: " + flagString); ++ String protoString = st.getIdentifier(); ++ proto = Protocol.value(protoString); ++ if (proto < 0) ++ throw st.exception("Invalid protocol: " + protoString); ++ String algString = st.getIdentifier(); ++ alg = DNSSEC.Algorithm.value(algString); ++ if (alg < 0) ++ throw st.exception("Invalid algorithm: " + algString); ++ /* If this is a null KEY, there's no key data */ ++ if ((flags & Flags.USE_MASK) == Flags.NOKEY) ++ key = null; ++ else ++ key = st.getBase64(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/KXRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/KXRecord.java +new file mode 100644 +index 0000000..481d21b +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/KXRecord.java +@@ -0,0 +1,51 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Key Exchange - delegation of authority ++ * ++ * @author Brian Wellington ++ */ ++ ++public class KXRecord extends U16NameBase { ++ ++private static final long serialVersionUID = 7448568832769757809L; ++ ++KXRecord() {} ++ ++Record ++getObject() { ++ return new KXRecord(); ++} ++ ++/** ++ * Creates a KX Record from the given data ++ * @param preference The preference of this KX. Records with lower priority ++ * are preferred. ++ * @param target The host that authority is delegated to ++ */ ++public ++KXRecord(Name name, int dclass, long ttl, int preference, Name target) { ++ super(name, Type.KX, dclass, ttl, preference, "preference", ++ target, "target"); ++} ++ ++/** Returns the target of the KX record */ ++public Name ++getTarget() { ++ return getNameField(); ++} ++ ++/** Returns the preference of this KX record */ ++public int ++getPreference() { ++ return getU16Field(); ++} ++ ++public Name ++getAdditionalName() { ++ return getNameField(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/LOCRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/LOCRecord.java +new file mode 100644 +index 0000000..4eddc15 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/LOCRecord.java +@@ -0,0 +1,314 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.text.*; ++ ++/** ++ * Location - describes the physical location of hosts, networks, subnets. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class LOCRecord extends Record { ++ ++private static final long serialVersionUID = 9058224788126750409L; ++ ++private static NumberFormat w2, w3; ++ ++private long size, hPrecision, vPrecision; ++private long latitude, longitude, altitude; ++ ++static { ++ w2 = new DecimalFormat(); ++ w2.setMinimumIntegerDigits(2); ++ ++ w3 = new DecimalFormat(); ++ w3.setMinimumIntegerDigits(3); ++} ++ ++LOCRecord() {} ++ ++Record ++getObject() { ++ return new LOCRecord(); ++} ++ ++/** ++ * Creates an LOC Record from the given data ++ * @param latitude The latitude of the center of the sphere ++ * @param longitude The longitude of the center of the sphere ++ * @param altitude The altitude of the center of the sphere, in m ++ * @param size The diameter of a sphere enclosing the described entity, in m. ++ * @param hPrecision The horizontal precision of the data, in m. ++ * @param vPrecision The vertical precision of the data, in m. ++*/ ++public ++LOCRecord(Name name, int dclass, long ttl, double latitude, double longitude, ++ double altitude, double size, double hPrecision, double vPrecision) ++{ ++ super(name, Type.LOC, dclass, ttl); ++ this.latitude = (long)(latitude * 3600 * 1000 + (1L << 31)); ++ this.longitude = (long)(longitude * 3600 * 1000 + (1L << 31)); ++ this.altitude = (long)((altitude + 100000) * 100); ++ this.size = (long)(size * 100); ++ this.hPrecision = (long)(hPrecision * 100); ++ this.vPrecision = (long)(vPrecision * 100); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ int version; ++ ++ version = in.readU8(); ++ if (version != 0) ++ throw new WireParseException("Invalid LOC version"); ++ ++ size = parseLOCformat(in.readU8()); ++ hPrecision = parseLOCformat(in.readU8()); ++ vPrecision = parseLOCformat(in.readU8()); ++ latitude = in.readU32(); ++ longitude = in.readU32(); ++ altitude = in.readU32(); ++} ++ ++private double ++parseFixedPoint(String s) ++{ ++ if (s.matches("^-?\\d+$")) ++ return Integer.parseInt(s); ++ else if (s.matches("^-?\\d+\\.\\d*$")) { ++ String [] parts = s.split("\\."); ++ double value = Integer.parseInt(parts[0]); ++ double fraction = Integer.parseInt(parts[1]); ++ if (value < 0) ++ fraction *= -1; ++ int digits = parts[1].length(); ++ return value + (fraction / Math.pow(10, digits)); ++ } else ++ throw new NumberFormatException(); ++} ++ ++private long ++parsePosition(Tokenizer st, String type) throws IOException { ++ boolean isLatitude = type.equals("latitude"); ++ int deg = 0, min = 0; ++ double sec = 0; ++ long value; ++ String s; ++ ++ deg = st.getUInt16(); ++ if (deg > 180 || (deg > 90 && isLatitude)) ++ throw st.exception("Invalid LOC " + type + " degrees"); ++ ++ s = st.getString(); ++ try { ++ min = Integer.parseInt(s); ++ if (min < 0 || min > 59) ++ throw st.exception("Invalid LOC " + type + " minutes"); ++ s = st.getString(); ++ sec = parseFixedPoint(s); ++ if (sec < 0 || sec >= 60) ++ throw st.exception("Invalid LOC " + type + " seconds"); ++ s = st.getString(); ++ } catch (NumberFormatException e) { ++ } ++ ++ if (s.length() != 1) ++ throw st.exception("Invalid LOC " + type); ++ ++ value = (long) (1000 * (sec + 60L * (min + 60L * deg))); ++ ++ char c = Character.toUpperCase(s.charAt(0)); ++ if ((isLatitude && c == 'S') || (!isLatitude && c == 'W')) ++ value = -value; ++ else if ((isLatitude && c != 'N') || (!isLatitude && c != 'E')) ++ throw st.exception("Invalid LOC " + type); ++ ++ value += (1L << 31); ++ ++ return value; ++} ++ ++private long ++parseDouble(Tokenizer st, String type, boolean required, long min, long max, ++ long defaultValue) ++throws IOException ++{ ++ Tokenizer.Token token = st.get(); ++ if (token.isEOL()) { ++ if (required) ++ throw st.exception("Invalid LOC " + type); ++ st.unget(); ++ return defaultValue; ++ } ++ String s = token.value; ++ if (s.length() > 1 && s.charAt(s.length() - 1) == 'm') ++ s = s.substring(0, s.length() - 1); ++ try { ++ long value = (long)(100 * parseFixedPoint(s)); ++ if (value < min || value > max) ++ throw st.exception("Invalid LOC " + type); ++ return value; ++ } ++ catch (NumberFormatException e) { ++ throw st.exception("Invalid LOC " + type); ++ } ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ latitude = parsePosition(st, "latitude"); ++ longitude = parsePosition(st, "longitude"); ++ altitude = parseDouble(st, "altitude", true, ++ -10000000, 4284967295L, 0) + 10000000; ++ size = parseDouble(st, "size", false, 0, 9000000000L, 100); ++ hPrecision = parseDouble(st, "horizontal precision", false, ++ 0, 9000000000L, 1000000); ++ vPrecision = parseDouble(st, "vertical precision", false, ++ 0, 9000000000L, 1000); ++} ++ ++private void ++renderFixedPoint(StringBuffer sb, NumberFormat formatter, long value, ++ long divisor) ++{ ++ sb.append(value / divisor); ++ value %= divisor; ++ if (value != 0) { ++ sb.append("."); ++ sb.append(formatter.format(value)); ++ } ++} ++ ++private String ++positionToString(long value, char pos, char neg) { ++ StringBuffer sb = new StringBuffer(); ++ char direction; ++ ++ long temp = value - (1L << 31); ++ if (temp < 0) { ++ temp = -temp; ++ direction = neg; ++ } else ++ direction = pos; ++ ++ sb.append(temp / (3600 * 1000)); /* degrees */ ++ temp = temp % (3600 * 1000); ++ sb.append(" "); ++ ++ sb.append(temp / (60 * 1000)); /* minutes */ ++ temp = temp % (60 * 1000); ++ sb.append(" "); ++ ++ renderFixedPoint(sb, w3, temp, 1000); /* seconds */ ++ sb.append(" "); ++ ++ sb.append(direction); ++ ++ return sb.toString(); ++} ++ ++ ++/** Convert to a String */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ ++ /* Latitude */ ++ sb.append(positionToString(latitude, 'N', 'S')); ++ sb.append(" "); ++ ++ /* Latitude */ ++ sb.append(positionToString(longitude, 'E', 'W')); ++ sb.append(" "); ++ ++ /* Altitude */ ++ renderFixedPoint(sb, w2, altitude - 10000000, 100); ++ sb.append("m "); ++ ++ /* Size */ ++ renderFixedPoint(sb, w2, size, 100); ++ sb.append("m "); ++ ++ /* Horizontal precision */ ++ renderFixedPoint(sb, w2, hPrecision, 100); ++ sb.append("m "); ++ ++ /* Vertical precision */ ++ renderFixedPoint(sb, w2, vPrecision, 100); ++ sb.append("m"); ++ ++ return sb.toString(); ++} ++ ++/** Returns the latitude */ ++public double ++getLatitude() { ++ return ((double)(latitude - (1L << 31))) / (3600 * 1000); ++} ++ ++/** Returns the longitude */ ++public double ++getLongitude() { ++ return ((double)(longitude - (1L << 31))) / (3600 * 1000); ++} ++ ++/** Returns the altitude */ ++public double ++getAltitude() { ++ return ((double)(altitude - 10000000)) / 100; ++} ++ ++/** Returns the diameter of the enclosing sphere */ ++public double ++getSize() { ++ return ((double)size) / 100; ++} ++ ++/** Returns the horizontal precision */ ++public double ++getHPrecision() { ++ return ((double)hPrecision) / 100; ++} ++ ++/** Returns the horizontal precision */ ++public double ++getVPrecision() { ++ return ((double)vPrecision) / 100; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeU8(0); /* version */ ++ out.writeU8(toLOCformat(size)); ++ out.writeU8(toLOCformat(hPrecision)); ++ out.writeU8(toLOCformat(vPrecision)); ++ out.writeU32(latitude); ++ out.writeU32(longitude); ++ out.writeU32(altitude); ++} ++ ++private static long ++parseLOCformat(int b) throws WireParseException { ++ long out = b >> 4; ++ int exp = b & 0xF; ++ if (out > 9 || exp > 9) ++ throw new WireParseException("Invalid LOC Encoding"); ++ while (exp-- > 0) ++ out *= 10; ++ return (out); ++} ++ ++private int ++toLOCformat(long l) { ++ byte exp = 0; ++ while (l > 9) { ++ exp++; ++ l /= 10; ++ } ++ return (int)((l << 4) + exp); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Lookup.java b/external/asmack/build/src/trunk/org/xbill/DNS/Lookup.java +new file mode 100644 +index 0000000..0beba59 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Lookup.java +@@ -0,0 +1,647 @@ ++// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.util.*; ++import java.io.*; ++import java.net.*; ++ ++/** ++ * The Lookup object issues queries to caching DNS servers. The input consists ++ * of a name, an optional type, and an optional class. Caching is enabled ++ * by default and used when possible to reduce the number of DNS requests. ++ * A Resolver, which defaults to an ExtendedResolver initialized with the ++ * resolvers located by the ResolverConfig class, performs the queries. A ++ * search path of domain suffixes is used to resolve relative names, and is ++ * also determined by the ResolverConfig class. ++ * ++ * A Lookup object may be reused, but should not be used by multiple threads. ++ * ++ * @see Cache ++ * @see Resolver ++ * @see ResolverConfig ++ * ++ * @author Brian Wellington ++ */ ++ ++public final class Lookup { ++ ++private static Resolver defaultResolver; ++private static Name [] defaultSearchPath; ++private static Map defaultCaches; ++private static int defaultNdots; ++ ++private Resolver resolver; ++private Name [] searchPath; ++private Cache cache; ++private boolean temporary_cache; ++private int credibility; ++private Name name; ++private int type; ++private int dclass; ++private boolean verbose; ++private int iterations; ++private boolean foundAlias; ++private boolean done; ++private boolean doneCurrent; ++private List aliases; ++private Record [] answers; ++private int result; ++private String error; ++private boolean nxdomain; ++private boolean badresponse; ++private String badresponse_error; ++private boolean networkerror; ++private boolean timedout; ++private boolean nametoolong; ++private boolean referral; ++ ++private static final Name [] noAliases = new Name[0]; ++ ++/** The lookup was successful. */ ++public static final int SUCCESSFUL = 0; ++ ++/** ++ * The lookup failed due to a data or server error. Repeating the lookup ++ * would not be helpful. ++ */ ++public static final int UNRECOVERABLE = 1; ++ ++/** ++ * The lookup failed due to a network error. Repeating the lookup may be ++ * helpful. ++ */ ++public static final int TRY_AGAIN = 2; ++ ++/** The host does not exist. */ ++public static final int HOST_NOT_FOUND = 3; ++ ++/** The host exists, but has no records associated with the queried type. */ ++public static final int TYPE_NOT_FOUND = 4; ++ ++public static synchronized void ++refreshDefault() { ++ ++ try { ++ defaultResolver = new ExtendedResolver(); ++ } ++ catch (UnknownHostException e) { ++ throw new RuntimeException("Failed to initialize resolver"); ++ } ++ defaultSearchPath = ResolverConfig.getCurrentConfig().searchPath(); ++ defaultCaches = new HashMap(); ++ defaultNdots = ResolverConfig.getCurrentConfig().ndots(); ++} ++ ++static { ++ refreshDefault(); ++} ++ ++/** ++ * Gets the Resolver that will be used as the default by future Lookups. ++ * @return The default resolver. ++ */ ++public static synchronized Resolver ++getDefaultResolver() { ++ return defaultResolver; ++} ++ ++/** ++ * Sets the default Resolver to be used as the default by future Lookups. ++ * @param resolver The default resolver. ++ */ ++public static synchronized void ++setDefaultResolver(Resolver resolver) { ++ defaultResolver = resolver; ++} ++ ++/** ++ * Gets the Cache that will be used as the default for the specified ++ * class by future Lookups. ++ * @param dclass The class whose cache is being retrieved. ++ * @return The default cache for the specified class. ++ */ ++public static synchronized Cache ++getDefaultCache(int dclass) { ++ DClass.check(dclass); ++ Cache c = (Cache) defaultCaches.get(Mnemonic.toInteger(dclass)); ++ if (c == null) { ++ c = new Cache(dclass); ++ defaultCaches.put(Mnemonic.toInteger(dclass), c); ++ } ++ return c; ++} ++ ++/** ++ * Sets the Cache to be used as the default for the specified class by future ++ * Lookups. ++ * @param cache The default cache for the specified class. ++ * @param dclass The class whose cache is being set. ++ */ ++public static synchronized void ++setDefaultCache(Cache cache, int dclass) { ++ DClass.check(dclass); ++ defaultCaches.put(Mnemonic.toInteger(dclass), cache); ++} ++ ++/** ++ * Gets the search path that will be used as the default by future Lookups. ++ * @return The default search path. ++ */ ++public static synchronized Name [] ++getDefaultSearchPath() { ++ return defaultSearchPath; ++} ++ ++/** ++ * Sets the search path to be used as the default by future Lookups. ++ * @param domains The default search path. ++ */ ++public static synchronized void ++setDefaultSearchPath(Name [] domains) { ++ defaultSearchPath = domains; ++} ++ ++/** ++ * Sets the search path that will be used as the default by future Lookups. ++ * @param domains The default search path. ++ * @throws TextParseException A name in the array is not a valid DNS name. ++ */ ++public static synchronized void ++setDefaultSearchPath(String [] domains) throws TextParseException { ++ if (domains == null) { ++ defaultSearchPath = null; ++ return; ++ } ++ Name [] newdomains = new Name[domains.length]; ++ for (int i = 0; i < domains.length; i++) ++ newdomains[i] = Name.fromString(domains[i], Name.root); ++ defaultSearchPath = newdomains; ++} ++ ++private final void ++reset() { ++ iterations = 0; ++ foundAlias = false; ++ done = false; ++ doneCurrent = false; ++ aliases = null; ++ answers = null; ++ result = -1; ++ error = null; ++ nxdomain = false; ++ badresponse = false; ++ badresponse_error = null; ++ networkerror = false; ++ timedout = false; ++ nametoolong = false; ++ referral = false; ++ if (temporary_cache) ++ cache.clearCache(); ++} ++ ++/** ++ * Create a Lookup object that will find records of the given name, type, ++ * and class. The lookup will use the default cache, resolver, and search ++ * path, and look for records that are reasonably credible. ++ * @param name The name of the desired records ++ * @param type The type of the desired records ++ * @param dclass The class of the desired records ++ * @throws IllegalArgumentException The type is a meta type other than ANY. ++ * @see Cache ++ * @see Resolver ++ * @see Credibility ++ * @see Name ++ * @see Type ++ * @see DClass ++ */ ++public ++Lookup(Name name, int type, int dclass) { ++ Type.check(type); ++ DClass.check(dclass); ++ if (!Type.isRR(type) && type != Type.ANY) ++ throw new IllegalArgumentException("Cannot query for " + ++ "meta-types other than ANY"); ++ this.name = name; ++ this.type = type; ++ this.dclass = dclass; ++ synchronized (Lookup.class) { ++ this.resolver = getDefaultResolver(); ++ this.searchPath = getDefaultSearchPath(); ++ this.cache = getDefaultCache(dclass); ++ } ++ this.credibility = Credibility.NORMAL; ++ this.verbose = Options.check("verbose"); ++ this.result = -1; ++} ++ ++/** ++ * Create a Lookup object that will find records of the given name and type ++ * in the IN class. ++ * @param name The name of the desired records ++ * @param type The type of the desired records ++ * @throws IllegalArgumentException The type is a meta type other than ANY. ++ * @see #Lookup(Name,int,int) ++ */ ++public ++Lookup(Name name, int type) { ++ this(name, type, DClass.IN); ++} ++ ++/** ++ * Create a Lookup object that will find records of type A at the given name ++ * in the IN class. ++ * @param name The name of the desired records ++ * @see #Lookup(Name,int,int) ++ */ ++public ++Lookup(Name name) { ++ this(name, Type.A, DClass.IN); ++} ++ ++/** ++ * Create a Lookup object that will find records of the given name, type, ++ * and class. ++ * @param name The name of the desired records ++ * @param type The type of the desired records ++ * @param dclass The class of the desired records ++ * @throws TextParseException The name is not a valid DNS name ++ * @throws IllegalArgumentException The type is a meta type other than ANY. ++ * @see #Lookup(Name,int,int) ++ */ ++public ++Lookup(String name, int type, int dclass) throws TextParseException { ++ this(Name.fromString(name), type, dclass); ++} ++ ++/** ++ * Create a Lookup object that will find records of the given name and type ++ * in the IN class. ++ * @param name The name of the desired records ++ * @param type The type of the desired records ++ * @throws TextParseException The name is not a valid DNS name ++ * @throws IllegalArgumentException The type is a meta type other than ANY. ++ * @see #Lookup(Name,int,int) ++ */ ++public ++Lookup(String name, int type) throws TextParseException { ++ this(Name.fromString(name), type, DClass.IN); ++} ++ ++/** ++ * Create a Lookup object that will find records of type A at the given name ++ * in the IN class. ++ * @param name The name of the desired records ++ * @throws TextParseException The name is not a valid DNS name ++ * @see #Lookup(Name,int,int) ++ */ ++public ++Lookup(String name) throws TextParseException { ++ this(Name.fromString(name), Type.A, DClass.IN); ++} ++ ++/** ++ * Sets the resolver to use when performing this lookup. This overrides the ++ * default value. ++ * @param resolver The resolver to use. ++ */ ++public void ++setResolver(Resolver resolver) { ++ this.resolver = resolver; ++} ++ ++/** ++ * Sets the search path to use when performing this lookup. This overrides the ++ * default value. ++ * @param domains An array of names containing the search path. ++ */ ++public void ++setSearchPath(Name [] domains) { ++ this.searchPath = domains; ++} ++ ++/** ++ * Sets the search path to use when performing this lookup. This overrides the ++ * default value. ++ * @param domains An array of names containing the search path. ++ * @throws TextParseException A name in the array is not a valid DNS name. ++ */ ++public void ++setSearchPath(String [] domains) throws TextParseException { ++ if (domains == null) { ++ this.searchPath = null; ++ return; ++ } ++ Name [] newdomains = new Name[domains.length]; ++ for (int i = 0; i < domains.length; i++) ++ newdomains[i] = Name.fromString(domains[i], Name.root); ++ this.searchPath = newdomains; ++} ++ ++/** ++ * Sets the cache to use when performing this lookup. This overrides the ++ * default value. If the results of this lookup should not be permanently ++ * cached, null can be provided here. ++ * @param cache The cache to use. ++ */ ++public void ++setCache(Cache cache) { ++ if (cache == null) { ++ this.cache = new Cache(dclass); ++ this.temporary_cache = true; ++ } else { ++ this.cache = cache; ++ this.temporary_cache = false; ++ } ++} ++ ++/** ++ * Sets ndots to use when performing this lookup, overriding the default value. ++ * Specifically, this refers to the number of "dots" which, if present in a ++ * name, indicate that a lookup for the absolute name should be attempted ++ * before appending any search path elements. ++ * @param ndots The ndots value to use, which must be greater than or equal to ++ * 0. ++ */ ++public void ++setNdots(int ndots) { ++ if (ndots < 0) ++ throw new IllegalArgumentException("Illegal ndots value: " + ++ ndots); ++ defaultNdots = ndots; ++} ++ ++/** ++ * Sets the minimum credibility level that will be accepted when performing ++ * the lookup. This defaults to Credibility.NORMAL. ++ * @param credibility The minimum credibility level. ++ */ ++public void ++setCredibility(int credibility) { ++ this.credibility = credibility; ++} ++ ++private void ++follow(Name name, Name oldname) { ++ foundAlias = true; ++ badresponse = false; ++ networkerror = false; ++ timedout = false; ++ nxdomain = false; ++ referral = false; ++ iterations++; ++ if (iterations >= 6 || name.equals(oldname)) { ++ result = UNRECOVERABLE; ++ error = "CNAME loop"; ++ done = true; ++ return; ++ } ++ if (aliases == null) ++ aliases = new ArrayList(); ++ aliases.add(oldname); ++ lookup(name); ++} ++ ++private void ++processResponse(Name name, SetResponse response) { ++ if (response.isSuccessful()) { ++ RRset [] rrsets = response.answers(); ++ List l = new ArrayList(); ++ Iterator it; ++ int i; ++ ++ for (i = 0; i < rrsets.length; i++) { ++ it = rrsets[i].rrs(); ++ while (it.hasNext()) ++ l.add(it.next()); ++ } ++ ++ result = SUCCESSFUL; ++ answers = (Record []) l.toArray(new Record[l.size()]); ++ done = true; ++ } else if (response.isNXDOMAIN()) { ++ nxdomain = true; ++ doneCurrent = true; ++ if (iterations > 0) { ++ result = HOST_NOT_FOUND; ++ done = true; ++ } ++ } else if (response.isNXRRSET()) { ++ result = TYPE_NOT_FOUND; ++ answers = null; ++ done = true; ++ } else if (response.isCNAME()) { ++ CNAMERecord cname = response.getCNAME(); ++ follow(cname.getTarget(), name); ++ } else if (response.isDNAME()) { ++ DNAMERecord dname = response.getDNAME(); ++ try { ++ follow(name.fromDNAME(dname), name); ++ } catch (NameTooLongException e) { ++ result = UNRECOVERABLE; ++ error = "Invalid DNAME target"; ++ done = true; ++ } ++ } else if (response.isDelegation()) { ++ // We shouldn't get a referral. Ignore it. ++ referral = true; ++ } ++} ++ ++private void ++lookup(Name current) { ++ SetResponse sr = cache.lookupRecords(current, type, credibility); ++ if (verbose) { ++ System.err.println("lookup " + current + " " + ++ Type.string(type)); ++ System.err.println(sr); ++ } ++ processResponse(current, sr); ++ if (done || doneCurrent) ++ return; ++ ++ Record question = Record.newRecord(current, type, dclass); ++ Message query = Message.newQuery(question); ++ Message response = null; ++ try { ++ response = resolver.send(query); ++ } ++ catch (IOException e) { ++ // A network error occurred. Press on. ++ if (e instanceof InterruptedIOException) ++ timedout = true; ++ else ++ networkerror = true; ++ return; ++ } ++ int rcode = response.getHeader().getRcode(); ++ if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN) { ++ // The server we contacted is broken or otherwise unhelpful. ++ // Press on. ++ badresponse = true; ++ badresponse_error = Rcode.string(rcode); ++ return; ++ } ++ ++ if (!query.getQuestion().equals(response.getQuestion())) { ++ // The answer doesn't match the question. That's not good. ++ badresponse = true; ++ badresponse_error = "response does not match query"; ++ return; ++ } ++ ++ sr = cache.addMessage(response); ++ if (sr == null) ++ sr = cache.lookupRecords(current, type, credibility); ++ if (verbose) { ++ System.err.println("queried " + current + " " + ++ Type.string(type)); ++ System.err.println(sr); ++ } ++ processResponse(current, sr); ++} ++ ++private void ++resolve(Name current, Name suffix) { ++ doneCurrent = false; ++ Name tname = null; ++ if (suffix == null) ++ tname = current; ++ else { ++ try { ++ tname = Name.concatenate(current, suffix); ++ } ++ catch (NameTooLongException e) { ++ nametoolong = true; ++ return; ++ } ++ } ++ lookup(tname); ++} ++ ++/** ++ * Performs the lookup, using the specified Cache, Resolver, and search path. ++ * @return The answers, or null if none are found. ++ */ ++public Record [] ++run() { ++ if (done) ++ reset(); ++ if (name.isAbsolute()) ++ resolve(name, null); ++ else if (searchPath == null) ++ resolve(name, Name.root); ++ else { ++ if (name.labels() > defaultNdots) ++ resolve(name, Name.root); ++ if (done) ++ return answers; ++ ++ for (int i = 0; i < searchPath.length; i++) { ++ resolve(name, searchPath[i]); ++ if (done) ++ return answers; ++ else if (foundAlias) ++ break; ++ } ++ } ++ if (!done) { ++ if (badresponse) { ++ result = TRY_AGAIN; ++ error = badresponse_error; ++ done = true; ++ } else if (timedout) { ++ result = TRY_AGAIN; ++ error = "timed out"; ++ done = true; ++ } else if (networkerror) { ++ result = TRY_AGAIN; ++ error = "network error"; ++ done = true; ++ } else if (nxdomain) { ++ result = HOST_NOT_FOUND; ++ done = true; ++ } else if (referral) { ++ result = UNRECOVERABLE; ++ error = "referral"; ++ done = true; ++ } else if (nametoolong) { ++ result = UNRECOVERABLE; ++ error = "name too long"; ++ done = true; ++ } ++ } ++ return answers; ++} ++ ++private void ++checkDone() { ++ if (done && result != -1) ++ return; ++ StringBuffer sb = new StringBuffer("Lookup of " + name + " "); ++ if (dclass != DClass.IN) ++ sb.append(DClass.string(dclass) + " "); ++ sb.append(Type.string(type) + " isn't done"); ++ throw new IllegalStateException(sb.toString()); ++} ++ ++/** ++ * Returns the answers from the lookup. ++ * @return The answers, or null if none are found. ++ * @throws IllegalStateException The lookup has not completed. ++ */ ++public Record [] ++getAnswers() { ++ checkDone(); ++ return answers; ++} ++ ++/** ++ * Returns all known aliases for this name. Whenever a CNAME/DNAME is ++ * followed, an alias is added to this array. The last element in this ++ * array will be the owner name for records in the answer, if there are any. ++ * @return The aliases. ++ * @throws IllegalStateException The lookup has not completed. ++ */ ++public Name [] ++getAliases() { ++ checkDone(); ++ if (aliases == null) ++ return noAliases; ++ return (Name []) aliases.toArray(new Name[aliases.size()]); ++} ++ ++/** ++ * Returns the result code of the lookup. ++ * @return The result code, which can be SUCCESSFUL, UNRECOVERABLE, TRY_AGAIN, ++ * HOST_NOT_FOUND, or TYPE_NOT_FOUND. ++ * @throws IllegalStateException The lookup has not completed. ++ */ ++public int ++getResult() { ++ checkDone(); ++ return result; ++} ++ ++/** ++ * Returns an error string describing the result code of this lookup. ++ * @return A string, which may either directly correspond the result code ++ * or be more specific. ++ * @throws IllegalStateException The lookup has not completed. ++ */ ++public String ++getErrorString() { ++ checkDone(); ++ if (error != null) ++ return error; ++ switch (result) { ++ case SUCCESSFUL: return "successful"; ++ case UNRECOVERABLE: return "unrecoverable error"; ++ case TRY_AGAIN: return "try again"; ++ case HOST_NOT_FOUND: return "host not found"; ++ case TYPE_NOT_FOUND: return "type not found"; ++ } ++ throw new IllegalStateException("unknown result"); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/MBRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/MBRecord.java +new file mode 100644 +index 0000000..6b65edf +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/MBRecord.java +@@ -0,0 +1,42 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Mailbox Record - specifies a host containing a mailbox. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class MBRecord extends SingleNameBase { ++ ++private static final long serialVersionUID = 532349543479150419L; ++ ++MBRecord() {} ++ ++Record ++getObject() { ++ return new MBRecord(); ++} ++ ++/** ++ * Creates a new MB Record with the given data ++ * @param mailbox The host containing the mailbox for the domain. ++ */ ++public ++MBRecord(Name name, int dclass, long ttl, Name mailbox) { ++ super(name, Type.MB, dclass, ttl, mailbox, "mailbox"); ++} ++ ++/** Gets the mailbox for the domain */ ++public Name ++getMailbox() { ++ return getSingleName(); ++} ++ ++public Name ++getAdditionalName() { ++ return getSingleName(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/MDRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/MDRecord.java +new file mode 100644 +index 0000000..dbf51af +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/MDRecord.java +@@ -0,0 +1,43 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Mail Destination Record - specifies a mail agent which delivers mail ++ * for a domain (obsolete) ++ * ++ * @author Brian Wellington ++ */ ++ ++public class MDRecord extends SingleNameBase { ++ ++private static final long serialVersionUID = 5268878603762942202L; ++ ++MDRecord() {} ++ ++Record ++getObject() { ++ return new MDRecord(); ++} ++ ++/** ++ * Creates a new MD Record with the given data ++ * @param mailAgent The mail agent that delivers mail for the domain. ++ */ ++public ++MDRecord(Name name, int dclass, long ttl, Name mailAgent) { ++ super(name, Type.MD, dclass, ttl, mailAgent, "mail agent"); ++} ++ ++/** Gets the mail agent for the domain */ ++public Name ++getMailAgent() { ++ return getSingleName(); ++} ++ ++public Name ++getAdditionalName() { ++ return getSingleName(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/MFRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/MFRecord.java +new file mode 100644 +index 0000000..ff293d7 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/MFRecord.java +@@ -0,0 +1,43 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Mail Forwarder Record - specifies a mail agent which forwards mail ++ * for a domain (obsolete) ++ * ++ * @author Brian Wellington ++ */ ++ ++public class MFRecord extends SingleNameBase { ++ ++private static final long serialVersionUID = -6670449036843028169L; ++ ++MFRecord() {} ++ ++Record ++getObject() { ++ return new MFRecord(); ++} ++ ++/** ++ * Creates a new MF Record with the given data ++ * @param mailAgent The mail agent that forwards mail for the domain. ++ */ ++public ++MFRecord(Name name, int dclass, long ttl, Name mailAgent) { ++ super(name, Type.MF, dclass, ttl, mailAgent, "mail agent"); ++} ++ ++/** Gets the mail agent for the domain */ ++public Name ++getMailAgent() { ++ return getSingleName(); ++} ++ ++public Name ++getAdditionalName() { ++ return getSingleName(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/MGRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/MGRecord.java +new file mode 100644 +index 0000000..5752f49 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/MGRecord.java +@@ -0,0 +1,38 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Mail Group Record - specifies a mailbox which is a member of a mail group. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class MGRecord extends SingleNameBase { ++ ++private static final long serialVersionUID = -3980055550863644582L; ++ ++MGRecord() {} ++ ++Record ++getObject() { ++ return new MGRecord(); ++} ++ ++/** ++ * Creates a new MG Record with the given data ++ * @param mailbox The mailbox that is a member of the group specified by the ++ * domain. ++ */ ++public ++MGRecord(Name name, int dclass, long ttl, Name mailbox) { ++ super(name, Type.MG, dclass, ttl, mailbox, "mailbox"); ++} ++ ++/** Gets the mailbox in the mail group specified by the domain */ ++public Name ++getMailbox() { ++ return getSingleName(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/MINFORecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/MINFORecord.java +new file mode 100644 +index 0000000..4324cda +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/MINFORecord.java +@@ -0,0 +1,90 @@ ++// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++/** ++ * Mailbox information Record - lists the address responsible for a mailing ++ * list/mailbox and the address to receive error messages relating to the ++ * mailing list/mailbox. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class MINFORecord extends Record { ++ ++private static final long serialVersionUID = -3962147172340353796L; ++ ++private Name responsibleAddress; ++private Name errorAddress; ++ ++MINFORecord() {} ++ ++Record ++getObject() { ++ return new MINFORecord(); ++} ++ ++/** ++ * Creates an MINFO Record from the given data ++ * @param responsibleAddress The address responsible for the ++ * mailing list/mailbox. ++ * @param errorAddress The address to receive error messages relating to the ++ * mailing list/mailbox. ++ */ ++public ++MINFORecord(Name name, int dclass, long ttl, ++ Name responsibleAddress, Name errorAddress) ++{ ++ super(name, Type.MINFO, dclass, ttl); ++ ++ this.responsibleAddress = checkName("responsibleAddress", ++ responsibleAddress); ++ this.errorAddress = checkName("errorAddress", errorAddress); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ responsibleAddress = new Name(in); ++ errorAddress = new Name(in); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ responsibleAddress = st.getName(origin); ++ errorAddress = st.getName(origin); ++} ++ ++/** Converts the MINFO Record to a String */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(responsibleAddress); ++ sb.append(" "); ++ sb.append(errorAddress); ++ return sb.toString(); ++} ++ ++/** Gets the address responsible for the mailing list/mailbox. */ ++public Name ++getResponsibleAddress() { ++ return responsibleAddress; ++} ++ ++/** ++ * Gets the address to receive error messages relating to the mailing ++ * list/mailbox. ++ */ ++public Name ++getErrorAddress() { ++ return errorAddress; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ responsibleAddress.toWire(out, null, canonical); ++ errorAddress.toWire(out, null, canonical); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/MRRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/MRRecord.java +new file mode 100644 +index 0000000..a7ff4fc +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/MRRecord.java +@@ -0,0 +1,38 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Mailbox Rename Record - specifies a rename of a mailbox. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class MRRecord extends SingleNameBase { ++ ++private static final long serialVersionUID = -5617939094209927533L; ++ ++MRRecord() {} ++ ++Record ++getObject() { ++ return new MRRecord(); ++} ++ ++/** ++ * Creates a new MR Record with the given data ++ * @param newName The new name of the mailbox specified by the domain. ++ * domain. ++ */ ++public ++MRRecord(Name name, int dclass, long ttl, Name newName) { ++ super(name, Type.MR, dclass, ttl, newName, "new name"); ++} ++ ++/** Gets the new name of the mailbox specified by the domain */ ++public Name ++getNewName() { ++ return getSingleName(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/MXRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/MXRecord.java +new file mode 100644 +index 0000000..111977d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/MXRecord.java +@@ -0,0 +1,57 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Mail Exchange - specifies where mail to a domain is sent ++ * ++ * @author Brian Wellington ++ */ ++ ++public class MXRecord extends U16NameBase { ++ ++private static final long serialVersionUID = 2914841027584208546L; ++ ++MXRecord() {} ++ ++Record ++getObject() { ++ return new MXRecord(); ++} ++ ++/** ++ * Creates an MX Record from the given data ++ * @param priority The priority of this MX. Records with lower priority ++ * are preferred. ++ * @param target The host that mail is sent to ++ */ ++public ++MXRecord(Name name, int dclass, long ttl, int priority, Name target) { ++ super(name, Type.MX, dclass, ttl, priority, "priority", ++ target, "target"); ++} ++ ++/** Returns the target of the MX record */ ++public Name ++getTarget() { ++ return getNameField(); ++} ++ ++/** Returns the priority of this MX record */ ++public int ++getPriority() { ++ return getU16Field(); ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeU16(u16Field); ++ nameField.toWire(out, c, canonical); ++} ++ ++public Name ++getAdditionalName() { ++ return getNameField(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Master.java b/external/asmack/build/src/trunk/org/xbill/DNS/Master.java +new file mode 100644 +index 0000000..c795a9c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Master.java +@@ -0,0 +1,427 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.util.*; ++ ++/** ++ * A DNS master file parser. This incrementally parses the file, returning ++ * one record at a time. When directives are seen, they are added to the ++ * state and used when parsing future records. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class Master { ++ ++private Name origin; ++private File file; ++private Record last = null; ++private long defaultTTL; ++private Master included = null; ++private Tokenizer st; ++private int currentType; ++private int currentDClass; ++private long currentTTL; ++private boolean needSOATTL; ++ ++private Generator generator; ++private List generators; ++private boolean noExpandGenerate; ++ ++Master(File file, Name origin, long initialTTL) throws IOException { ++ if (origin != null && !origin.isAbsolute()) { ++ throw new RelativeNameException(origin); ++ } ++ this.file = file; ++ st = new Tokenizer(file); ++ this.origin = origin; ++ defaultTTL = initialTTL; ++} ++ ++/** ++ * Initializes the master file reader and opens the specified master file. ++ * @param filename The master file. ++ * @param origin The initial origin to append to relative names. ++ * @param ttl The initial default TTL. ++ * @throws IOException The master file could not be opened. ++ */ ++public ++Master(String filename, Name origin, long ttl) throws IOException { ++ this(new File(filename), origin, ttl); ++} ++ ++/** ++ * Initializes the master file reader and opens the specified master file. ++ * @param filename The master file. ++ * @param origin The initial origin to append to relative names. ++ * @throws IOException The master file could not be opened. ++ */ ++public ++Master(String filename, Name origin) throws IOException { ++ this(new File(filename), origin, -1); ++} ++ ++/** ++ * Initializes the master file reader and opens the specified master file. ++ * @param filename The master file. ++ * @throws IOException The master file could not be opened. ++ */ ++public ++Master(String filename) throws IOException { ++ this(new File(filename), null, -1); ++} ++ ++/** ++ * Initializes the master file reader. ++ * @param in The input stream containing a master file. ++ * @param origin The initial origin to append to relative names. ++ * @param ttl The initial default TTL. ++ */ ++public ++Master(InputStream in, Name origin, long ttl) { ++ if (origin != null && !origin.isAbsolute()) { ++ throw new RelativeNameException(origin); ++ } ++ st = new Tokenizer(in); ++ this.origin = origin; ++ defaultTTL = ttl; ++} ++ ++/** ++ * Initializes the master file reader. ++ * @param in The input stream containing a master file. ++ * @param origin The initial origin to append to relative names. ++ */ ++public ++Master(InputStream in, Name origin) { ++ this(in, origin, -1); ++} ++ ++/** ++ * Initializes the master file reader. ++ * @param in The input stream containing a master file. ++ */ ++public ++Master(InputStream in) { ++ this(in, null, -1); ++} ++ ++private Name ++parseName(String s, Name origin) throws TextParseException { ++ try { ++ return Name.fromString(s, origin); ++ } ++ catch (TextParseException e) { ++ throw st.exception(e.getMessage()); ++ } ++} ++ ++private void ++parseTTLClassAndType() throws IOException { ++ String s; ++ boolean seen_class = false; ++ ++ ++ // This is a bit messy, since any of the following are legal: ++ // class ttl type ++ // ttl class type ++ // class type ++ // ttl type ++ // type ++ seen_class = false; ++ s = st.getString(); ++ if ((currentDClass = DClass.value(s)) >= 0) { ++ s = st.getString(); ++ seen_class = true; ++ } ++ ++ currentTTL = -1; ++ try { ++ currentTTL = TTL.parseTTL(s); ++ s = st.getString(); ++ } ++ catch (NumberFormatException e) { ++ if (defaultTTL >= 0) ++ currentTTL = defaultTTL; ++ else if (last != null) ++ currentTTL = last.getTTL(); ++ } ++ ++ if (!seen_class) { ++ if ((currentDClass = DClass.value(s)) >= 0) { ++ s = st.getString(); ++ } else { ++ currentDClass = DClass.IN; ++ } ++ } ++ ++ if ((currentType = Type.value(s)) < 0) ++ throw st.exception("Invalid type '" + s + "'"); ++ ++ // BIND allows a missing TTL for the initial SOA record, and uses ++ // the SOA minimum value. If the SOA is not the first record, ++ // this is an error. ++ if (currentTTL < 0) { ++ if (currentType != Type.SOA) ++ throw st.exception("missing TTL"); ++ needSOATTL = true; ++ currentTTL = 0; ++ } ++} ++ ++private long ++parseUInt32(String s) { ++ if (!Character.isDigit(s.charAt(0))) ++ return -1; ++ try { ++ long l = Long.parseLong(s); ++ if (l < 0 || l > 0xFFFFFFFFL) ++ return -1; ++ return l; ++ } ++ catch (NumberFormatException e) { ++ return -1; ++ } ++} ++ ++private void ++startGenerate() throws IOException { ++ String s; ++ int n; ++ ++ // The first field is of the form start-end[/step] ++ // Regexes would be useful here. ++ s = st.getIdentifier(); ++ n = s.indexOf("-"); ++ if (n < 0) ++ throw st.exception("Invalid $GENERATE range specifier: " + s); ++ String startstr = s.substring(0, n); ++ String endstr = s.substring(n + 1); ++ String stepstr = null; ++ n = endstr.indexOf("/"); ++ if (n >= 0) { ++ stepstr = endstr.substring(n + 1); ++ endstr = endstr.substring(0, n); ++ } ++ long start = parseUInt32(startstr); ++ long end = parseUInt32(endstr); ++ long step; ++ if (stepstr != null) ++ step = parseUInt32(stepstr); ++ else ++ step = 1; ++ if (start < 0 || end < 0 || start > end || step <= 0) ++ throw st.exception("Invalid $GENERATE range specifier: " + s); ++ ++ // The next field is the name specification. ++ String nameSpec = st.getIdentifier(); ++ ++ // Then the ttl/class/type, in the same form as a normal record. ++ // Only some types are supported. ++ parseTTLClassAndType(); ++ if (!Generator.supportedType(currentType)) ++ throw st.exception("$GENERATE does not support " + ++ Type.string(currentType) + " records"); ++ ++ // Next comes the rdata specification. ++ String rdataSpec = st.getIdentifier(); ++ ++ // That should be the end. However, we don't want to move past the ++ // line yet, so put back the EOL after reading it. ++ st.getEOL(); ++ st.unget(); ++ ++ generator = new Generator(start, end, step, nameSpec, ++ currentType, currentDClass, currentTTL, ++ rdataSpec, origin); ++ if (generators == null) ++ generators = new ArrayList(1); ++ generators.add(generator); ++} ++ ++private void ++endGenerate() throws IOException { ++ // Read the EOL that we put back before. ++ st.getEOL(); ++ ++ generator = null; ++} ++ ++private Record ++nextGenerated() throws IOException { ++ try { ++ return generator.nextRecord(); ++ } ++ catch (Tokenizer.TokenizerException e) { ++ throw st.exception("Parsing $GENERATE: " + e.getBaseMessage()); ++ } ++ catch (TextParseException e) { ++ throw st.exception("Parsing $GENERATE: " + e.getMessage()); ++ } ++} ++ ++/** ++ * Returns the next record in the master file. This will process any ++ * directives before the next record. ++ * @return The next record. ++ * @throws IOException The master file could not be read, or was syntactically ++ * invalid. ++ */ ++public Record ++_nextRecord() throws IOException { ++ Tokenizer.Token token; ++ String s; ++ ++ if (included != null) { ++ Record rec = included.nextRecord(); ++ if (rec != null) ++ return rec; ++ included = null; ++ } ++ if (generator != null) { ++ Record rec = nextGenerated(); ++ if (rec != null) ++ return rec; ++ endGenerate(); ++ } ++ while (true) { ++ Name name; ++ ++ token = st.get(true, false); ++ if (token.type == Tokenizer.WHITESPACE) { ++ Tokenizer.Token next = st.get(); ++ if (next.type == Tokenizer.EOL) ++ continue; ++ else if (next.type == Tokenizer.EOF) ++ return null; ++ else ++ st.unget(); ++ if (last == null) ++ throw st.exception("no owner"); ++ name = last.getName(); ++ } ++ else if (token.type == Tokenizer.EOL) ++ continue; ++ else if (token.type == Tokenizer.EOF) ++ return null; ++ else if (((String) token.value).charAt(0) == '$') { ++ s = token.value; ++ ++ if (s.equalsIgnoreCase("$ORIGIN")) { ++ origin = st.getName(Name.root); ++ st.getEOL(); ++ continue; ++ } else if (s.equalsIgnoreCase("$TTL")) { ++ defaultTTL = st.getTTL(); ++ st.getEOL(); ++ continue; ++ } else if (s.equalsIgnoreCase("$INCLUDE")) { ++ String filename = st.getString(); ++ File newfile; ++ if (file != null) { ++ String parent = file.getParent(); ++ newfile = new File(parent, filename); ++ } else { ++ newfile = new File(filename); ++ } ++ Name incorigin = origin; ++ token = st.get(); ++ if (token.isString()) { ++ incorigin = parseName(token.value, ++ Name.root); ++ st.getEOL(); ++ } ++ included = new Master(newfile, incorigin, ++ defaultTTL); ++ /* ++ * If we continued, we wouldn't be looking in ++ * the new file. Recursing works better. ++ */ ++ return nextRecord(); ++ } else if (s.equalsIgnoreCase("$GENERATE")) { ++ if (generator != null) ++ throw new IllegalStateException ++ ("cannot nest $GENERATE"); ++ startGenerate(); ++ if (noExpandGenerate) { ++ endGenerate(); ++ continue; ++ } ++ return nextGenerated(); ++ } else { ++ throw st.exception("Invalid directive: " + s); ++ } ++ } else { ++ s = token.value; ++ name = parseName(s, origin); ++ if (last != null && name.equals(last.getName())) { ++ name = last.getName(); ++ } ++ } ++ ++ parseTTLClassAndType(); ++ last = Record.fromString(name, currentType, currentDClass, ++ currentTTL, st, origin); ++ if (needSOATTL) { ++ long ttl = ((SOARecord)last).getMinimum(); ++ last.setTTL(ttl); ++ defaultTTL = ttl; ++ needSOATTL = false; ++ } ++ return last; ++ } ++} ++ ++/** ++ * Returns the next record in the master file. This will process any ++ * directives before the next record. ++ * @return The next record. ++ * @throws IOException The master file could not be read, or was syntactically ++ * invalid. ++ */ ++public Record ++nextRecord() throws IOException { ++ Record rec = null; ++ try { ++ rec = _nextRecord(); ++ } ++ finally { ++ if (rec == null) { ++ st.close(); ++ } ++ } ++ return rec; ++} ++ ++/** ++ * Specifies whether $GENERATE statements should be expanded. Whether ++ * expanded or not, the specifications for generated records are available ++ * by calling {@link #generators}. This must be called before a $GENERATE ++ * statement is seen during iteration to have an effect. ++ */ ++public void ++expandGenerate(boolean wantExpand) { ++ noExpandGenerate = !wantExpand; ++} ++ ++/** ++ * Returns an iterator over the generators specified in the master file; that ++ * is, the parsed contents of $GENERATE statements. ++ * @see Generator ++ */ ++public Iterator ++generators() { ++ if (generators != null) ++ return Collections.unmodifiableList(generators).iterator(); ++ else ++ return Collections.EMPTY_LIST.iterator(); ++} ++ ++protected void ++finalize() { ++ st.close(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Message.java b/external/asmack/build/src/trunk/org/xbill/DNS/Message.java +new file mode 100644 +index 0000000..7967675 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Message.java +@@ -0,0 +1,607 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.util.*; ++import java.io.*; ++ ++/** ++ * A DNS Message. A message is the basic unit of communication between ++ * the client and server of a DNS operation. A message consists of a Header ++ * and 4 message sections. ++ * @see Resolver ++ * @see Header ++ * @see Section ++ * ++ * @author Brian Wellington ++ */ ++ ++public class Message implements Cloneable { ++ ++/** The maximum length of a message in wire format. */ ++public static final int MAXLENGTH = 65535; ++ ++private Header header; ++private List [] sections; ++private int size; ++private TSIG tsigkey; ++private TSIGRecord querytsig; ++private int tsigerror; ++ ++int tsigstart; ++int tsigState; ++int sig0start; ++ ++/* The message was not signed */ ++static final int TSIG_UNSIGNED = 0; ++ ++/* The message was signed and verification succeeded */ ++static final int TSIG_VERIFIED = 1; ++ ++/* The message was an unsigned message in multiple-message response */ ++static final int TSIG_INTERMEDIATE = 2; ++ ++/* The message was signed and no verification was attempted. */ ++static final int TSIG_SIGNED = 3; ++ ++/* ++ * The message was signed and verification failed, or was not signed ++ * when it should have been. ++ */ ++static final int TSIG_FAILED = 4; ++ ++private static Record [] emptyRecordArray = new Record[0]; ++private static RRset [] emptyRRsetArray = new RRset[0]; ++ ++private ++Message(Header header) { ++ sections = new List[4]; ++ this.header = header; ++} ++ ++/** Creates a new Message with the specified Message ID */ ++public ++Message(int id) { ++ this(new Header(id)); ++} ++ ++/** Creates a new Message with a random Message ID */ ++public ++Message() { ++ this(new Header()); ++} ++ ++/** ++ * Creates a new Message with a random Message ID suitable for sending as a ++ * query. ++ * @param r A record containing the question ++ */ ++public static Message ++newQuery(Record r) { ++ Message m = new Message(); ++ m.header.setOpcode(Opcode.QUERY); ++ m.header.setFlag(Flags.RD); ++ m.addRecord(r, Section.QUESTION); ++ return m; ++} ++ ++/** ++ * Creates a new Message to contain a dynamic update. A random Message ID ++ * and the zone are filled in. ++ * @param zone The zone to be updated ++ */ ++public static Message ++newUpdate(Name zone) { ++ return new Update(zone); ++} ++ ++Message(DNSInput in) throws IOException { ++ this(new Header(in)); ++ boolean isUpdate = (header.getOpcode() == Opcode.UPDATE); ++ boolean truncated = header.getFlag(Flags.TC); ++ try { ++ for (int i = 0; i < 4; i++) { ++ int count = header.getCount(i); ++ if (count > 0) ++ sections[i] = new ArrayList(count); ++ for (int j = 0; j < count; j++) { ++ int pos = in.current(); ++ Record rec = Record.fromWire(in, i, isUpdate); ++ sections[i].add(rec); ++ if (rec.getType() == Type.TSIG) ++ tsigstart = pos; ++ if (rec.getType() == Type.SIG && ++ ((SIGRecord) rec).getTypeCovered() == 0) ++ sig0start = pos; ++ } ++ } ++ } catch (WireParseException e) { ++ if (!truncated) ++ throw e; ++ } ++ size = in.current(); ++} ++ ++/** ++ * Creates a new Message from its DNS wire format representation ++ * @param b A byte array containing the DNS Message. ++ */ ++public ++Message(byte [] b) throws IOException { ++ this(new DNSInput(b)); ++} ++ ++/** ++ * Replaces the Header with a new one. ++ * @see Header ++ */ ++public void ++setHeader(Header h) { ++ header = h; ++} ++ ++/** ++ * Retrieves the Header. ++ * @see Header ++ */ ++public Header ++getHeader() { ++ return header; ++} ++ ++/** ++ * Adds a record to a section of the Message, and adjusts the header. ++ * @see Record ++ * @see Section ++ */ ++public void ++addRecord(Record r, int section) { ++ if (sections[section] == null) ++ sections[section] = new LinkedList(); ++ header.incCount(section); ++ sections[section].add(r); ++} ++ ++/** ++ * Removes a record from a section of the Message, and adjusts the header. ++ * @see Record ++ * @see Section ++ */ ++public boolean ++removeRecord(Record r, int section) { ++ if (sections[section] != null && sections[section].remove(r)) { ++ header.decCount(section); ++ return true; ++ } ++ else ++ return false; ++} ++ ++/** ++ * Removes all records from a section of the Message, and adjusts the header. ++ * @see Record ++ * @see Section ++ */ ++public void ++removeAllRecords(int section) { ++ sections[section] = null; ++ header.setCount(section, 0); ++} ++ ++/** ++ * Determines if the given record is already present in the given section. ++ * @see Record ++ * @see Section ++ */ ++public boolean ++findRecord(Record r, int section) { ++ return (sections[section] != null && sections[section].contains(r)); ++} ++ ++/** ++ * Determines if the given record is already present in any section. ++ * @see Record ++ * @see Section ++ */ ++public boolean ++findRecord(Record r) { ++ for (int i = Section.ANSWER; i <= Section.ADDITIONAL; i++) ++ if (sections[i] != null && sections[i].contains(r)) ++ return true; ++ return false; ++} ++ ++/** ++ * Determines if an RRset with the given name and type is already ++ * present in the given section. ++ * @see RRset ++ * @see Section ++ */ ++public boolean ++findRRset(Name name, int type, int section) { ++ if (sections[section] == null) ++ return false; ++ for (int i = 0; i < sections[section].size(); i++) { ++ Record r = (Record) sections[section].get(i); ++ if (r.getType() == type && name.equals(r.getName())) ++ return true; ++ } ++ return false; ++} ++ ++/** ++ * Determines if an RRset with the given name and type is already ++ * present in any section. ++ * @see RRset ++ * @see Section ++ */ ++public boolean ++findRRset(Name name, int type) { ++ return (findRRset(name, type, Section.ANSWER) || ++ findRRset(name, type, Section.AUTHORITY) || ++ findRRset(name, type, Section.ADDITIONAL)); ++} ++ ++/** ++ * Returns the first record in the QUESTION section. ++ * @see Record ++ * @see Section ++ */ ++public Record ++getQuestion() { ++ List l = sections[Section.QUESTION]; ++ if (l == null || l.size() == 0) ++ return null; ++ return (Record) l.get(0); ++} ++ ++/** ++ * Returns the TSIG record from the ADDITIONAL section, if one is present. ++ * @see TSIGRecord ++ * @see TSIG ++ * @see Section ++ */ ++public TSIGRecord ++getTSIG() { ++ int count = header.getCount(Section.ADDITIONAL); ++ if (count == 0) ++ return null; ++ List l = sections[Section.ADDITIONAL]; ++ Record rec = (Record) l.get(count - 1); ++ if (rec.type != Type.TSIG) ++ return null; ++ return (TSIGRecord) rec; ++} ++ ++/** ++ * Was this message signed by a TSIG? ++ * @see TSIG ++ */ ++public boolean ++isSigned() { ++ return (tsigState == TSIG_SIGNED || ++ tsigState == TSIG_VERIFIED || ++ tsigState == TSIG_FAILED); ++} ++ ++/** ++ * If this message was signed by a TSIG, was the TSIG verified? ++ * @see TSIG ++ */ ++public boolean ++isVerified() { ++ return (tsigState == TSIG_VERIFIED); ++} ++ ++/** ++ * Returns the OPT record from the ADDITIONAL section, if one is present. ++ * @see OPTRecord ++ * @see Section ++ */ ++public OPTRecord ++getOPT() { ++ Record [] additional = getSectionArray(Section.ADDITIONAL); ++ for (int i = 0; i < additional.length; i++) ++ if (additional[i] instanceof OPTRecord) ++ return (OPTRecord) additional[i]; ++ return null; ++} ++ ++/** ++ * Returns the message's rcode (error code). This incorporates the EDNS ++ * extended rcode. ++ */ ++public int ++getRcode() { ++ int rcode = header.getRcode(); ++ OPTRecord opt = getOPT(); ++ if (opt != null) ++ rcode += (opt.getExtendedRcode() << 4); ++ return rcode; ++} ++ ++/** ++ * Returns an array containing all records in the given section, or an ++ * empty array if the section is empty. ++ * @see Record ++ * @see Section ++ */ ++public Record [] ++getSectionArray(int section) { ++ if (sections[section] == null) ++ return emptyRecordArray; ++ List l = sections[section]; ++ return (Record []) l.toArray(new Record[l.size()]); ++} ++ ++private static boolean ++sameSet(Record r1, Record r2) { ++ return (r1.getRRsetType() == r2.getRRsetType() && ++ r1.getDClass() == r2.getDClass() && ++ r1.getName().equals(r2.getName())); ++} ++ ++/** ++ * Returns an array containing all records in the given section grouped into ++ * RRsets. ++ * @see RRset ++ * @see Section ++ */ ++public RRset [] ++getSectionRRsets(int section) { ++ if (sections[section] == null) ++ return emptyRRsetArray; ++ List sets = new LinkedList(); ++ Record [] recs = getSectionArray(section); ++ Set hash = new HashSet(); ++ for (int i = 0; i < recs.length; i++) { ++ Name name = recs[i].getName(); ++ boolean newset = true; ++ if (hash.contains(name)) { ++ for (int j = sets.size() - 1; j >= 0; j--) { ++ RRset set = (RRset) sets.get(j); ++ if (set.getType() == recs[i].getRRsetType() && ++ set.getDClass() == recs[i].getDClass() && ++ set.getName().equals(name)) ++ { ++ set.addRR(recs[i]); ++ newset = false; ++ break; ++ } ++ } ++ } ++ if (newset) { ++ RRset set = new RRset(recs[i]); ++ sets.add(set); ++ hash.add(name); ++ } ++ } ++ return (RRset []) sets.toArray(new RRset[sets.size()]); ++} ++ ++void ++toWire(DNSOutput out) { ++ header.toWire(out); ++ Compression c = new Compression(); ++ for (int i = 0; i < 4; i++) { ++ if (sections[i] == null) ++ continue; ++ for (int j = 0; j < sections[i].size(); j++) { ++ Record rec = (Record)sections[i].get(j); ++ rec.toWire(out, i, c); ++ } ++ } ++} ++ ++/* Returns the number of records not successfully rendered. */ ++private int ++sectionToWire(DNSOutput out, int section, Compression c, ++ int maxLength) ++{ ++ int n = sections[section].size(); ++ int pos = out.current(); ++ int rendered = 0; ++ Record lastrec = null; ++ ++ for (int i = 0; i < n; i++) { ++ Record rec = (Record)sections[section].get(i); ++ if (lastrec != null && !sameSet(rec, lastrec)) { ++ pos = out.current(); ++ rendered = i; ++ } ++ lastrec = rec; ++ rec.toWire(out, section, c); ++ if (out.current() > maxLength) { ++ out.jump(pos); ++ return n - rendered; ++ } ++ } ++ return 0; ++} ++ ++/* Returns true if the message could be rendered. */ ++private boolean ++toWire(DNSOutput out, int maxLength) { ++ if (maxLength < Header.LENGTH) ++ return false; ++ ++ Header newheader = null; ++ ++ int tempMaxLength = maxLength; ++ if (tsigkey != null) ++ tempMaxLength -= tsigkey.recordLength(); ++ ++ int startpos = out.current(); ++ header.toWire(out); ++ Compression c = new Compression(); ++ for (int i = 0; i < 4; i++) { ++ int skipped; ++ if (sections[i] == null) ++ continue; ++ skipped = sectionToWire(out, i, c, tempMaxLength); ++ if (skipped != 0) { ++ if (newheader == null) ++ newheader = (Header) header.clone(); ++ if (i != Section.ADDITIONAL) ++ newheader.setFlag(Flags.TC); ++ int count = newheader.getCount(i); ++ newheader.setCount(i, count - skipped); ++ for (int j = i + 1; j < 4; j++) ++ newheader.setCount(j, 0); ++ ++ out.save(); ++ out.jump(startpos); ++ newheader.toWire(out); ++ out.restore(); ++ break; ++ } ++ } ++ ++ if (tsigkey != null) { ++ TSIGRecord tsigrec = tsigkey.generate(this, out.toByteArray(), ++ tsigerror, querytsig); ++ ++ if (newheader == null) ++ newheader = (Header) header.clone(); ++ tsigrec.toWire(out, Section.ADDITIONAL, c); ++ newheader.incCount(Section.ADDITIONAL); ++ ++ out.save(); ++ out.jump(startpos); ++ newheader.toWire(out); ++ out.restore(); ++ } ++ ++ return true; ++} ++ ++/** ++ * Returns an array containing the wire format representation of the Message. ++ */ ++public byte [] ++toWire() { ++ DNSOutput out = new DNSOutput(); ++ toWire(out); ++ size = out.current(); ++ return out.toByteArray(); ++} ++ ++/** ++ * Returns an array containing the wire format representation of the Message ++ * with the specified maximum length. This will generate a truncated ++ * message (with the TC bit) if the message doesn't fit, and will also ++ * sign the message with the TSIG key set by a call to setTSIG(). This ++ * method may return null if the message could not be rendered at all; this ++ * could happen if maxLength is smaller than a DNS header, for example. ++ * @param maxLength The maximum length of the message. ++ * @return The wire format of the message, or null if the message could not be ++ * rendered into the specified length. ++ * @see Flags ++ * @see TSIG ++ */ ++public byte [] ++toWire(int maxLength) { ++ DNSOutput out = new DNSOutput(); ++ toWire(out, maxLength); ++ size = out.current(); ++ return out.toByteArray(); ++} ++ ++/** ++ * Sets the TSIG key and other necessary information to sign a message. ++ * @param key The TSIG key. ++ * @param error The value of the TSIG error field. ++ * @param querytsig If this is a response, the TSIG from the request. ++ */ ++public void ++setTSIG(TSIG key, int error, TSIGRecord querytsig) { ++ this.tsigkey = key; ++ this.tsigerror = error; ++ this.querytsig = querytsig; ++} ++ ++/** ++ * Returns the size of the message. Only valid if the message has been ++ * converted to or from wire format. ++ */ ++public int ++numBytes() { ++ return size; ++} ++ ++/** ++ * Converts the given section of the Message to a String. ++ * @see Section ++ */ ++public String ++sectionToString(int i) { ++ if (i > 3) ++ return null; ++ ++ StringBuffer sb = new StringBuffer(); ++ ++ Record [] records = getSectionArray(i); ++ for (int j = 0; j < records.length; j++) { ++ Record rec = records[j]; ++ if (i == Section.QUESTION) { ++ sb.append(";;\t" + rec.name); ++ sb.append(", type = " + Type.string(rec.type)); ++ sb.append(", class = " + DClass.string(rec.dclass)); ++ } ++ else ++ sb.append(rec); ++ sb.append("\n"); ++ } ++ return sb.toString(); ++} ++ ++/** ++ * Converts the Message to a String. ++ */ ++public String ++toString() { ++ StringBuffer sb = new StringBuffer(); ++ OPTRecord opt = getOPT(); ++ if (opt != null) ++ sb.append(header.toStringWithRcode(getRcode()) + "\n"); ++ else ++ sb.append(header + "\n"); ++ if (isSigned()) { ++ sb.append(";; TSIG "); ++ if (isVerified()) ++ sb.append("ok"); ++ else ++ sb.append("invalid"); ++ sb.append('\n'); ++ } ++ for (int i = 0; i < 4; i++) { ++ if (header.getOpcode() != Opcode.UPDATE) ++ sb.append(";; " + Section.longString(i) + ":\n"); ++ else ++ sb.append(";; " + Section.updString(i) + ":\n"); ++ sb.append(sectionToString(i) + "\n"); ++ } ++ sb.append(";; Message size: " + numBytes() + " bytes"); ++ return sb.toString(); ++} ++ ++/** ++ * Creates a copy of this Message. This is done by the Resolver before adding ++ * TSIG and OPT records, for example. ++ * @see Resolver ++ * @see TSIGRecord ++ * @see OPTRecord ++ */ ++public Object ++clone() { ++ Message m = new Message(); ++ for (int i = 0; i < sections.length; i++) { ++ if (sections[i] != null) ++ m.sections[i] = new LinkedList(sections[i]); ++ } ++ m.header = (Header) header.clone(); ++ m.size = size; ++ return m; ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Mnemonic.java b/external/asmack/build/src/trunk/org/xbill/DNS/Mnemonic.java +new file mode 100644 +index 0000000..dd60f62 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Mnemonic.java +@@ -0,0 +1,210 @@ ++// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.util.HashMap; ++ ++/** ++ * A utility class for converting between numeric codes and mnemonics ++ * for those codes. Mnemonics are case insensitive. ++ * ++ * @author Brian Wellington ++ */ ++ ++class Mnemonic { ++ ++private static Integer cachedInts[] = new Integer[64]; ++ ++static { ++ for (int i = 0; i < cachedInts.length; i++) { ++ cachedInts[i] = new Integer(i); ++ } ++} ++ ++/* Strings are case-sensitive. */ ++static final int CASE_SENSITIVE = 1; ++ ++/* Strings will be stored/searched for in uppercase. */ ++static final int CASE_UPPER = 2; ++ ++/* Strings will be stored/searched for in lowercase. */ ++static final int CASE_LOWER = 3; ++ ++private HashMap strings; ++private HashMap values; ++private String description; ++private int wordcase; ++private String prefix; ++private int max; ++private boolean numericok; ++ ++/** ++ * Creates a new Mnemonic table. ++ * @param description A short description of the mnemonic to use when ++ * @param wordcase Whether to convert strings into uppercase, lowercase, ++ * or leave them unchanged. ++ * throwing exceptions. ++ */ ++public ++Mnemonic(String description, int wordcase) { ++ this.description = description; ++ this.wordcase = wordcase; ++ strings = new HashMap(); ++ values = new HashMap(); ++ max = Integer.MAX_VALUE; ++} ++ ++/** Sets the maximum numeric value */ ++public void ++setMaximum(int max) { ++ this.max = max; ++} ++ ++/** ++ * Sets the prefix to use when converting to and from values that don't ++ * have mnemonics. ++ */ ++public void ++setPrefix(String prefix) { ++ this.prefix = sanitize(prefix); ++} ++ ++/** ++ * Sets whether numeric values stored in strings are acceptable. ++ */ ++public void ++setNumericAllowed(boolean numeric) { ++ this.numericok = numeric; ++} ++ ++/** ++ * Converts an int into a possibly cached Integer. ++ */ ++public static Integer ++toInteger(int val) { ++ if (val >= 0 && val < cachedInts.length) ++ return (cachedInts[val]); ++ return new Integer(val); ++} ++ ++/** ++ * Checks that a numeric value is within the range [0..max] ++ */ ++public void ++check(int val) { ++ if (val < 0 || val > max) { ++ throw new IllegalArgumentException(description + " " + val + ++ "is out of range"); ++ } ++} ++ ++/* Converts a String to the correct case. */ ++private String ++sanitize(String str) { ++ if (wordcase == CASE_UPPER) ++ return str.toUpperCase(); ++ else if (wordcase == CASE_LOWER) ++ return str.toLowerCase(); ++ return str; ++} ++ ++private int ++parseNumeric(String s) { ++ try { ++ int val = Integer.parseInt(s); ++ if (val >= 0 && val <= max) ++ return val; ++ } ++ catch (NumberFormatException e) { ++ } ++ return -1; ++} ++ ++/** ++ * Defines the text representation of a numeric value. ++ * @param val The numeric value ++ * @param string The text string ++ */ ++public void ++add(int val, String str) { ++ check(val); ++ Integer value = toInteger(val); ++ str = sanitize(str); ++ strings.put(str, value); ++ values.put(value, str); ++} ++ ++/** ++ * Defines an additional text representation of a numeric value. This will ++ * be used by getValue(), but not getText(). ++ * @param val The numeric value ++ * @param string The text string ++ */ ++public void ++addAlias(int val, String str) { ++ check(val); ++ Integer value = toInteger(val); ++ str = sanitize(str); ++ strings.put(str, value); ++} ++ ++/** ++ * Copies all mnemonics from one table into another. ++ * @param val The numeric value ++ * @param string The text string ++ * @throws IllegalArgumentException The wordcases of the Mnemonics do not ++ * match. ++ */ ++public void ++addAll(Mnemonic source) { ++ if (wordcase != source.wordcase) ++ throw new IllegalArgumentException(source.description + ++ ": wordcases do not match"); ++ strings.putAll(source.strings); ++ values.putAll(source.values); ++} ++ ++/** ++ * Gets the text mnemonic corresponding to a numeric value. ++ * @param val The numeric value ++ * @return The corresponding text mnemonic. ++ */ ++public String ++getText(int val) { ++ check(val); ++ String str = (String) values.get(toInteger(val)); ++ if (str != null) ++ return str; ++ str = Integer.toString(val); ++ if (prefix != null) ++ return prefix + str; ++ return str; ++} ++ ++/** ++ * Gets the numeric value corresponding to a text mnemonic. ++ * @param str The text mnemonic ++ * @return The corresponding numeric value, or -1 if there is none ++ */ ++public int ++getValue(String str) { ++ str = sanitize(str); ++ Integer value = (Integer) strings.get(str); ++ if (value != null) { ++ return value.intValue(); ++ } ++ if (prefix != null) { ++ if (str.startsWith(prefix)) { ++ int val = parseNumeric(str.substring(prefix.length())); ++ if (val >= 0) { ++ return val; ++ } ++ } ++ } ++ if (numericok) { ++ return parseNumeric(str); ++ } ++ return -1; ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NAPTRRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/NAPTRRecord.java +new file mode 100644 +index 0000000..da2ec6d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/NAPTRRecord.java +@@ -0,0 +1,154 @@ ++// Copyright (c) 2000-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++/** ++ * Name Authority Pointer Record - specifies rewrite rule, that when applied ++ * to an existing string will produce a new domain. ++ * ++ * @author Chuck Santos ++ */ ++ ++public class NAPTRRecord extends Record { ++ ++private static final long serialVersionUID = 5191232392044947002L; ++ ++private int order, preference; ++private byte [] flags, service, regexp; ++private Name replacement; ++ ++NAPTRRecord() {} ++ ++Record ++getObject() { ++ return new NAPTRRecord(); ++} ++ ++/** ++ * Creates an NAPTR Record from the given data ++ * @param order The order of this NAPTR. Records with lower order are ++ * preferred. ++ * @param preference The preference, used to select between records at the ++ * same order. ++ * @param flags The control aspects of the NAPTRRecord. ++ * @param service The service or protocol available down the rewrite path. ++ * @param regexp The regular/substitution expression. ++ * @param replacement The domain-name to query for the next DNS resource ++ * record, depending on the value of the flags field. ++ * @throws IllegalArgumentException One of the strings has invalid escapes ++ */ ++public ++NAPTRRecord(Name name, int dclass, long ttl, int order, int preference, ++ String flags, String service, String regexp, Name replacement) ++{ ++ super(name, Type.NAPTR, dclass, ttl); ++ this.order = checkU16("order", order); ++ this.preference = checkU16("preference", preference); ++ try { ++ this.flags = byteArrayFromString(flags); ++ this.service = byteArrayFromString(service); ++ this.regexp = byteArrayFromString(regexp); ++ } ++ catch (TextParseException e) { ++ throw new IllegalArgumentException(e.getMessage()); ++ } ++ this.replacement = checkName("replacement", replacement); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ order = in.readU16(); ++ preference = in.readU16(); ++ flags = in.readCountedString(); ++ service = in.readCountedString(); ++ regexp = in.readCountedString(); ++ replacement = new Name(in); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ order = st.getUInt16(); ++ preference = st.getUInt16(); ++ try { ++ flags = byteArrayFromString(st.getString()); ++ service = byteArrayFromString(st.getString()); ++ regexp = byteArrayFromString(st.getString()); ++ } ++ catch (TextParseException e) { ++ throw st.exception(e.getMessage()); ++ } ++ replacement = st.getName(origin); ++} ++ ++/** Converts rdata to a String */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(order); ++ sb.append(" "); ++ sb.append(preference); ++ sb.append(" "); ++ sb.append(byteArrayToString(flags, true)); ++ sb.append(" "); ++ sb.append(byteArrayToString(service, true)); ++ sb.append(" "); ++ sb.append(byteArrayToString(regexp, true)); ++ sb.append(" "); ++ sb.append(replacement); ++ return sb.toString(); ++} ++ ++/** Returns the order */ ++public int ++getOrder() { ++ return order; ++} ++ ++/** Returns the preference */ ++public int ++getPreference() { ++ return preference; ++} ++ ++/** Returns flags */ ++public String ++getFlags() { ++ return byteArrayToString(flags, false); ++} ++ ++/** Returns service */ ++public String ++getService() { ++ return byteArrayToString(service, false); ++} ++ ++/** Returns regexp */ ++public String ++getRegexp() { ++ return byteArrayToString(regexp, false); ++} ++ ++/** Returns the replacement domain-name */ ++public Name ++getReplacement() { ++ return replacement; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeU16(order); ++ out.writeU16(preference); ++ out.writeCountedString(flags); ++ out.writeCountedString(service); ++ out.writeCountedString(regexp); ++ replacement.toWire(out, null, canonical); ++} ++ ++public Name ++getAdditionalName() { ++ return replacement; ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NSAPRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/NSAPRecord.java +new file mode 100644 +index 0000000..a6b2031 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/NSAPRecord.java +@@ -0,0 +1,106 @@ ++// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import org.xbill.DNS.utils.*; ++ ++/** ++ * NSAP Address Record. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class NSAPRecord extends Record { ++ ++private static final long serialVersionUID = -1037209403185658593L; ++ ++private byte [] address; ++ ++NSAPRecord() {} ++ ++Record ++getObject() { ++ return new NSAPRecord(); ++} ++ ++private static final byte [] ++checkAndConvertAddress(String address) { ++ if (!address.substring(0, 2).equalsIgnoreCase("0x")) { ++ return null; ++ } ++ ByteArrayOutputStream bytes = new ByteArrayOutputStream(); ++ boolean partial = false; ++ int current = 0; ++ for (int i = 2; i < address.length(); i++) { ++ char c = address.charAt(i); ++ if (c == '.') { ++ continue; ++ } ++ int value = Character.digit(c, 16); ++ if (value == -1) { ++ return null; ++ } ++ if (partial) { ++ current += value; ++ bytes.write(current); ++ partial = false; ++ } else { ++ current = value << 4; ++ partial = true; ++ } ++ ++ } ++ if (partial) { ++ return null; ++ } ++ return bytes.toByteArray(); ++} ++ ++/** ++ * Creates an NSAP Record from the given data ++ * @param address The NSAP address. ++ * @throws IllegalArgumentException The address is not a valid NSAP address. ++ */ ++public ++NSAPRecord(Name name, int dclass, long ttl, String address) { ++ super(name, Type.NSAP, dclass, ttl); ++ this.address = checkAndConvertAddress(address); ++ if (this.address == null) { ++ throw new IllegalArgumentException("invalid NSAP address " + ++ address); ++ } ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ address = in.readByteArray(); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ String addr = st.getString(); ++ this.address = checkAndConvertAddress(addr); ++ if (this.address == null) ++ throw st.exception("invalid NSAP address " + addr); ++} ++ ++/** ++ * Returns the NSAP address. ++ */ ++public String ++getAddress() { ++ return byteArrayToString(address, false); ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeByteArray(address); ++} ++ ++String ++rrToString() { ++ return "0x" + base16.toString(address); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NSAP_PTRRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/NSAP_PTRRecord.java +new file mode 100644 +index 0000000..ecc609f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/NSAP_PTRRecord.java +@@ -0,0 +1,38 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * NSAP Pointer Record - maps a domain name representing an NSAP Address to ++ * a hostname. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class NSAP_PTRRecord extends SingleNameBase { ++ ++private static final long serialVersionUID = 2386284746382064904L; ++ ++NSAP_PTRRecord() {} ++ ++Record ++getObject() { ++ return new NSAP_PTRRecord(); ++} ++ ++/** ++ * Creates a new NSAP_PTR Record with the given data ++ * @param target The name of the host with this address ++ */ ++public ++NSAP_PTRRecord(Name name, int dclass, long ttl, Name target) { ++ super(name, Type.NSAP_PTR, dclass, ttl, target, "target"); ++} ++ ++/** Gets the target of the NSAP_PTR Record */ ++public Name ++getTarget() { ++ return getSingleName(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NSEC3PARAMRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/NSEC3PARAMRecord.java +new file mode 100644 +index 0000000..d663a62 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/NSEC3PARAMRecord.java +@@ -0,0 +1,165 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.IOException; ++import java.security.NoSuchAlgorithmException; ++ ++import org.xbill.DNS.utils.base16; ++ ++/** ++ * Next SECure name 3 Parameters - this record contains the parameters (hash ++ * algorithm, salt, iterations) used for a valid, complete NSEC3 chain present ++ * in a zone. Zones signed using NSEC3 must include this record at the zone apex ++ * to inform authoritative servers that NSEC3 is being used with the given ++ * parameters. ++ * ++ * @author Brian Wellington ++ * @author David Blacka ++ */ ++ ++public class NSEC3PARAMRecord extends Record { ++ ++private static final long serialVersionUID = -8689038598776316533L; ++ ++private int hashAlg; ++private int flags; ++private int iterations; ++private byte salt[]; ++ ++NSEC3PARAMRecord() {} ++ ++Record getObject() { ++ return new NSEC3PARAMRecord(); ++} ++ ++/** ++ * Creates an NSEC3PARAM record from the given data. ++ * ++ * @param name The ownername of the NSEC3PARAM record (generally the zone name). ++ * @param dclass The class. ++ * @param ttl The TTL. ++ * @param hashAlg The hash algorithm. ++ * @param flags The value of the flags field. ++ * @param iterations The number of hash iterations. ++ * @param salt The salt to use (may be null). ++ */ ++public NSEC3PARAMRecord(Name name, int dclass, long ttl, int hashAlg, ++ int flags, int iterations, byte [] salt) ++{ ++ super(name, Type.NSEC3PARAM, dclass, ttl); ++ this.hashAlg = checkU8("hashAlg", hashAlg); ++ this.flags = checkU8("flags", flags); ++ this.iterations = checkU16("iterations", iterations); ++ ++ if (salt != null) { ++ if (salt.length > 255) ++ throw new IllegalArgumentException("Invalid salt " + ++ "length"); ++ if (salt.length > 0) { ++ this.salt = new byte[salt.length]; ++ System.arraycopy(salt, 0, this.salt, 0, salt.length); ++ } ++ } ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ hashAlg = in.readU8(); ++ flags = in.readU8(); ++ iterations = in.readU16(); ++ ++ int salt_length = in.readU8(); ++ if (salt_length > 0) ++ salt = in.readByteArray(salt_length); ++ else ++ salt = null; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeU8(hashAlg); ++ out.writeU8(flags); ++ out.writeU16(iterations); ++ ++ if (salt != null) { ++ out.writeU8(salt.length); ++ out.writeByteArray(salt); ++ } else ++ out.writeU8(0); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException ++{ ++ hashAlg = st.getUInt8(); ++ flags = st.getUInt8(); ++ iterations = st.getUInt16(); ++ ++ String s = st.getString(); ++ if (s.equals("-")) ++ salt = null; ++ else { ++ st.unget(); ++ salt = st.getHexString(); ++ if (salt.length > 255) ++ throw st.exception("salt value too long"); ++ } ++} ++ ++/** Converts rdata to a String */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(hashAlg); ++ sb.append(' '); ++ sb.append(flags); ++ sb.append(' '); ++ sb.append(iterations); ++ sb.append(' '); ++ if (salt == null) ++ sb.append('-'); ++ else ++ sb.append(base16.toString(salt)); ++ ++ return sb.toString(); ++} ++ ++/** Returns the hash algorithm */ ++public int ++getHashAlgorithm() { ++ return hashAlg; ++} ++ ++/** Returns the flags */ ++public int ++getFlags() { ++ return flags; ++} ++ ++/** Returns the number of iterations */ ++public int ++getIterations() { ++ return iterations; ++} ++ ++/** Returns the salt */ ++public byte [] ++getSalt() ++{ ++ return salt; ++} ++ ++/** ++ * Hashes a name with the parameters of this NSEC3PARAM record. ++ * @param name The name to hash ++ * @return The hashed version of the name ++ * @throws NoSuchAlgorithmException The hash algorithm is unknown. ++ */ ++public byte [] ++hashName(Name name) throws NoSuchAlgorithmException ++{ ++ return NSEC3Record.hashName(name, hashAlg, iterations, salt); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NSEC3Record.java b/external/asmack/build/src/trunk/org/xbill/DNS/NSEC3Record.java +new file mode 100644 +index 0000000..aa086b8 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/NSEC3Record.java +@@ -0,0 +1,266 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.security.*; ++ ++import org.xbill.DNS.utils.*; ++ ++/** ++ * Next SECure name 3 - this record contains the next hashed name in an ++ * ordered list of hashed names in the zone, and a set of types for which ++ * records exist for this name. The presence of this record in a response ++ * signifies a negative response from a DNSSEC-signed zone. ++ * ++ * This replaces the NSEC and NXT records, when used. ++ * ++ * @author Brian Wellington ++ * @author David Blacka ++ */ ++ ++public class NSEC3Record extends Record { ++ ++public static class Flags { ++ /** ++ * NSEC3 flags identifiers. ++ */ ++ ++ private Flags() {} ++ ++ /** Unsigned delegation are not included in the NSEC3 chain. ++ * ++ */ ++ public static final int OPT_OUT = 0x01; ++} ++ ++public static class Digest { ++ private Digest() {} ++ ++ /** SHA-1 */ ++ public static final int SHA1 = 1; ++} ++ ++public static final int SHA1_DIGEST_ID = Digest.SHA1; ++ ++private static final long serialVersionUID = -7123504635968932855L; ++ ++private int hashAlg; ++private int flags; ++private int iterations; ++private byte [] salt; ++private byte [] next; ++private TypeBitmap types; ++ ++private static final base32 b32 = new base32(base32.Alphabet.BASE32HEX, ++ false, false); ++ ++NSEC3Record() {} ++ ++Record getObject() { ++ return new NSEC3Record(); ++} ++ ++/** ++ * Creates an NSEC3 record from the given data. ++ * ++ * @param name The ownername of the NSEC3 record (base32'd hash plus zonename). ++ * @param dclass The class. ++ * @param ttl The TTL. ++ * @param hashAlg The hash algorithm. ++ * @param flags The value of the flags field. ++ * @param iterations The number of hash iterations. ++ * @param salt The salt to use (may be null). ++ * @param next The next hash (may not be null). ++ * @param types The types present at the original ownername. ++ */ ++public NSEC3Record(Name name, int dclass, long ttl, int hashAlg, ++ int flags, int iterations, byte [] salt, byte [] next, ++ int [] types) ++{ ++ super(name, Type.NSEC3, dclass, ttl); ++ this.hashAlg = checkU8("hashAlg", hashAlg); ++ this.flags = checkU8("flags", flags); ++ this.iterations = checkU16("iterations", iterations); ++ ++ if (salt != null) { ++ if (salt.length > 255) ++ throw new IllegalArgumentException("Invalid salt"); ++ if (salt.length > 0) { ++ this.salt = new byte[salt.length]; ++ System.arraycopy(salt, 0, this.salt, 0, salt.length); ++ } ++ } ++ ++ if (next.length > 255) { ++ throw new IllegalArgumentException("Invalid next hash"); ++ } ++ this.next = new byte[next.length]; ++ System.arraycopy(next, 0, this.next, 0, next.length); ++ this.types = new TypeBitmap(types); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ hashAlg = in.readU8(); ++ flags = in.readU8(); ++ iterations = in.readU16(); ++ ++ int salt_length = in.readU8(); ++ if (salt_length > 0) ++ salt = in.readByteArray(salt_length); ++ else ++ salt = null; ++ ++ int next_length = in.readU8(); ++ next = in.readByteArray(next_length); ++ types = new TypeBitmap(in); ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeU8(hashAlg); ++ out.writeU8(flags); ++ out.writeU16(iterations); ++ ++ if (salt != null) { ++ out.writeU8(salt.length); ++ out.writeByteArray(salt); ++ } else ++ out.writeU8(0); ++ ++ out.writeU8(next.length); ++ out.writeByteArray(next); ++ types.toWire(out); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ hashAlg = st.getUInt8(); ++ flags = st.getUInt8(); ++ iterations = st.getUInt16(); ++ ++ String s = st.getString(); ++ if (s.equals("-")) ++ salt = null; ++ else { ++ st.unget(); ++ salt = st.getHexString(); ++ if (salt.length > 255) ++ throw st.exception("salt value too long"); ++ } ++ ++ next = st.getBase32String(b32); ++ types = new TypeBitmap(st); ++} ++ ++/** Converts rdata to a String */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(hashAlg); ++ sb.append(' '); ++ sb.append(flags); ++ sb.append(' '); ++ sb.append(iterations); ++ sb.append(' '); ++ if (salt == null) ++ sb.append('-'); ++ else ++ sb.append(base16.toString(salt)); ++ sb.append(' '); ++ sb.append(b32.toString(next)); ++ ++ if (!types.empty()) { ++ sb.append(' '); ++ sb.append(types.toString()); ++ } ++ ++ return sb.toString(); ++} ++ ++/** Returns the hash algorithm */ ++public int ++getHashAlgorithm() { ++ return hashAlg; ++} ++ ++/** Returns the flags */ ++public int ++getFlags() { ++ return flags; ++} ++ ++/** Returns the number of iterations */ ++public int ++getIterations() { ++ return iterations; ++} ++ ++/** Returns the salt */ ++public byte [] ++getSalt() ++{ ++ return salt; ++} ++ ++/** Returns the next hash */ ++public byte [] ++getNext() { ++ return next; ++} ++ ++ /** Returns the set of types defined for this name */ ++public int [] ++getTypes() { ++ return types.toArray(); ++} ++ ++/** Returns whether a specific type is in the set of types. */ ++public boolean ++hasType(int type) ++{ ++ return types.contains(type); ++} ++ ++static byte [] ++hashName(Name name, int hashAlg, int iterations, byte [] salt) ++throws NoSuchAlgorithmException ++{ ++ MessageDigest digest; ++ switch (hashAlg) { ++ case Digest.SHA1: ++ digest = MessageDigest.getInstance("sha-1"); ++ break; ++ default: ++ throw new NoSuchAlgorithmException("Unknown NSEC3 algorithm" + ++ "identifier: " + ++ hashAlg); ++ } ++ byte [] hash = null; ++ for (int i = 0; i <= iterations; i++) { ++ digest.reset(); ++ if (i == 0) ++ digest.update(name.toWireCanonical()); ++ else ++ digest.update(hash); ++ if (salt != null) ++ digest.update(salt); ++ hash = digest.digest(); ++ } ++ return hash; ++} ++ ++/** ++ * Hashes a name with the parameters of this NSEC3 record. ++ * @param name The name to hash ++ * @return The hashed version of the name ++ * @throws NoSuchAlgorithmException The hash algorithm is unknown. ++ */ ++public byte [] ++hashName(Name name) throws NoSuchAlgorithmException ++{ ++ return hashName(name, hashAlg, iterations, salt); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NSECRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/NSECRecord.java +new file mode 100644 +index 0000000..e523e37 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/NSECRecord.java +@@ -0,0 +1,98 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++/** ++ * Next SECure name - this record contains the following name in an ++ * ordered list of names in the zone, and a set of types for which ++ * records exist for this name. The presence of this record in a response ++ * signifies a negative response from a DNSSEC-signed zone. ++ * ++ * This replaces the NXT record. ++ * ++ * @author Brian Wellington ++ * @author David Blacka ++ */ ++ ++public class NSECRecord extends Record { ++ ++private static final long serialVersionUID = -5165065768816265385L; ++ ++private Name next; ++private TypeBitmap types; ++ ++NSECRecord() {} ++ ++Record ++getObject() { ++ return new NSECRecord(); ++} ++ ++/** ++ * Creates an NSEC Record from the given data. ++ * @param next The following name in an ordered list of the zone ++ * @param types An array containing the types present. ++ */ ++public ++NSECRecord(Name name, int dclass, long ttl, Name next, int [] types) { ++ super(name, Type.NSEC, dclass, ttl); ++ this.next = checkName("next", next); ++ for (int i = 0; i < types.length; i++) { ++ Type.check(types[i]); ++ } ++ this.types = new TypeBitmap(types); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ next = new Name(in); ++ types = new TypeBitmap(in); ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ // Note: The next name is not lowercased. ++ next.toWire(out, null, false); ++ types.toWire(out); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ next = st.getName(origin); ++ types = new TypeBitmap(st); ++} ++ ++/** Converts rdata to a String */ ++String ++rrToString() ++{ ++ StringBuffer sb = new StringBuffer(); ++ sb.append(next); ++ if (!types.empty()) { ++ sb.append(' '); ++ sb.append(types.toString()); ++ } ++ return sb.toString(); ++} ++ ++/** Returns the next name */ ++public Name ++getNext() { ++ return next; ++} ++ ++/** Returns the set of types defined for this name */ ++public int [] ++getTypes() { ++ return types.toArray(); ++} ++ ++/** Returns whether a specific type is in the set of types. */ ++public boolean ++hasType(int type) { ++ return types.contains(type); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NSIDOption.java b/external/asmack/build/src/trunk/org/xbill/DNS/NSIDOption.java +new file mode 100644 +index 0000000..7bcbcd5 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/NSIDOption.java +@@ -0,0 +1,29 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * The Name Server Identifier Option, define in RFC 5001. ++ * ++ * @see OPTRecord ++ * ++ * @author Brian Wellington ++ */ ++public class NSIDOption extends GenericEDNSOption { ++ ++private static final long serialVersionUID = 74739759292589056L; ++ ++NSIDOption() { ++ super(EDNSOption.Code.NSID); ++} ++ ++/** ++ * Construct an NSID option. ++ * @param data The contents of the option. ++ */ ++public ++NSIDOption(byte [] data) { ++ super(EDNSOption.Code.NSID, data); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NSRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/NSRecord.java +new file mode 100644 +index 0000000..2908da4 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/NSRecord.java +@@ -0,0 +1,42 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Name Server Record - contains the name server serving the named zone ++ * ++ * @author Brian Wellington ++ */ ++ ++public class NSRecord extends SingleCompressedNameBase { ++ ++private static final long serialVersionUID = 487170758138268838L; ++ ++NSRecord() {} ++ ++Record ++getObject() { ++ return new NSRecord(); ++} ++ ++/** ++ * Creates a new NS Record with the given data ++ * @param target The name server for the given domain ++ */ ++public ++NSRecord(Name name, int dclass, long ttl, Name target) { ++ super(name, Type.NS, dclass, ttl, target, "target"); ++} ++ ++/** Gets the target of the NS Record */ ++public Name ++getTarget() { ++ return getSingleName(); ++} ++ ++public Name ++getAdditionalName() { ++ return getSingleName(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NULLRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/NULLRecord.java +new file mode 100644 +index 0000000..fa46d61 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/NULLRecord.java +@@ -0,0 +1,67 @@ ++// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++/** ++ * The NULL Record. This has no defined purpose, but can be used to ++ * hold arbitrary data. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class NULLRecord extends Record { ++ ++private static final long serialVersionUID = -5796493183235216538L; ++ ++private byte [] data; ++ ++NULLRecord() {} ++ ++Record ++getObject() { ++ return new NULLRecord(); ++} ++ ++/** ++ * Creates a NULL record from the given data. ++ * @param data The contents of the record. ++ */ ++public ++NULLRecord(Name name, int dclass, long ttl, byte [] data) { ++ super(name, Type.NULL, dclass, ttl); ++ ++ if (data.length > 0xFFFF) { ++ throw new IllegalArgumentException("data must be <65536 bytes"); ++ } ++ this.data = data; ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ data = in.readByteArray(); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ throw st.exception("no defined text format for NULL records"); ++} ++ ++String ++rrToString() { ++ return unknownToString(data); ++} ++ ++/** Returns the contents of this record. */ ++public byte [] ++getData() { ++ return data; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeByteArray(data); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NXTRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/NXTRecord.java +new file mode 100644 +index 0000000..ad04e01 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/NXTRecord.java +@@ -0,0 +1,111 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.util.*; ++ ++/** ++ * Next name - this record contains the following name in an ordered list ++ * of names in the zone, and a set of types for which records exist for ++ * this name. The presence of this record in a response signifies a ++ * failed query for data in a DNSSEC-signed zone. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class NXTRecord extends Record { ++ ++private static final long serialVersionUID = -8851454400765507520L; ++ ++private Name next; ++private BitSet bitmap; ++ ++NXTRecord() {} ++ ++Record ++getObject() { ++ return new NXTRecord(); ++} ++ ++/** ++ * Creates an NXT Record from the given data ++ * @param next The following name in an ordered list of the zone ++ * @param bitmap The set of type for which records exist at this name ++*/ ++public ++NXTRecord(Name name, int dclass, long ttl, Name next, BitSet bitmap) { ++ super(name, Type.NXT, dclass, ttl); ++ this.next = checkName("next", next); ++ this.bitmap = bitmap; ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ next = new Name(in); ++ bitmap = new BitSet(); ++ int bitmapLength = in.remaining(); ++ for (int i = 0; i < bitmapLength; i++) { ++ int t = in.readU8(); ++ for (int j = 0; j < 8; j++) ++ if ((t & (1 << (7 - j))) != 0) ++ bitmap.set(i * 8 + j); ++ } ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ next = st.getName(origin); ++ bitmap = new BitSet(); ++ while (true) { ++ Tokenizer.Token t = st.get(); ++ if (!t.isString()) ++ break; ++ int typecode = Type.value(t.value, true); ++ if (typecode <= 0 || typecode > 128) ++ throw st.exception("Invalid type: " + t.value); ++ bitmap.set(typecode); ++ } ++ st.unget(); ++} ++ ++/** Converts rdata to a String */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(next); ++ int length = bitmap.length(); ++ for (short i = 0; i < length; i++) ++ if (bitmap.get(i)) { ++ sb.append(" "); ++ sb.append(Type.string(i)); ++ } ++ return sb.toString(); ++} ++ ++/** Returns the next name */ ++public Name ++getNext() { ++ return next; ++} ++ ++/** Returns the set of types defined for this name */ ++public BitSet ++getBitmap() { ++ return bitmap; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ next.toWire(out, null, canonical); ++ int length = bitmap.length(); ++ for (int i = 0, t = 0; i < length; i++) { ++ t |= (bitmap.get(i) ? (1 << (7 - i % 8)) : 0); ++ if (i % 8 == 7 || i == length - 1) { ++ out.writeU8(t); ++ t = 0; ++ } ++ } ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Name.java b/external/asmack/build/src/trunk/org/xbill/DNS/Name.java +new file mode 100644 +index 0000000..1331ad9 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Name.java +@@ -0,0 +1,822 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.text.*; ++ ++/** ++ * A representation of a domain name. It may either be absolute (fully ++ * qualified) or relative. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class Name implements Comparable, Serializable { ++ ++private static final long serialVersionUID = -7257019940971525644L; ++ ++private static final int LABEL_NORMAL = 0; ++private static final int LABEL_COMPRESSION = 0xC0; ++private static final int LABEL_MASK = 0xC0; ++ ++/* The name data */ ++private byte [] name; ++ ++/* ++ * Effectively an 8 byte array, where the low order byte stores the number ++ * of labels and the 7 higher order bytes store per-label offsets. ++ */ ++private long offsets; ++ ++/* Precomputed hashcode. */ ++private int hashcode; ++ ++private static final byte [] emptyLabel = new byte[] {(byte)0}; ++private static final byte [] wildLabel = new byte[] {(byte)1, (byte)'*'}; ++ ++/** The root name */ ++public static final Name root; ++ ++/** The root name */ ++public static final Name empty; ++ ++/** The maximum length of a Name */ ++private static final int MAXNAME = 255; ++ ++/** The maximum length of a label a Name */ ++private static final int MAXLABEL = 63; ++ ++/** The maximum number of labels in a Name */ ++private static final int MAXLABELS = 128; ++ ++/** The maximum number of cached offsets */ ++private static final int MAXOFFSETS = 7; ++ ++/* Used for printing non-printable characters */ ++private static final DecimalFormat byteFormat = new DecimalFormat(); ++ ++/* Used to efficiently convert bytes to lowercase */ ++private static final byte lowercase[] = new byte[256]; ++ ++/* Used in wildcard names. */ ++private static final Name wild; ++ ++static { ++ byteFormat.setMinimumIntegerDigits(3); ++ for (int i = 0; i < lowercase.length; i++) { ++ if (i < 'A' || i > 'Z') ++ lowercase[i] = (byte)i; ++ else ++ lowercase[i] = (byte)(i - 'A' + 'a'); ++ } ++ root = new Name(); ++ root.appendSafe(emptyLabel, 0, 1); ++ empty = new Name(); ++ empty.name = new byte[0]; ++ wild = new Name(); ++ wild.appendSafe(wildLabel, 0, 1); ++} ++ ++private ++Name() { ++} ++ ++private final void ++setoffset(int n, int offset) { ++ if (n >= MAXOFFSETS) ++ return; ++ int shift = 8 * (7 - n); ++ offsets &= (~(0xFFL << shift)); ++ offsets |= ((long)offset << shift); ++} ++ ++private final int ++offset(int n) { ++ if (n == 0 && getlabels() == 0) ++ return 0; ++ if (n < 0 || n >= getlabels()) ++ throw new IllegalArgumentException("label out of range"); ++ if (n < MAXOFFSETS) { ++ int shift = 8 * (7 - n); ++ return ((int)(offsets >>> shift) & 0xFF); ++ } else { ++ int pos = offset(MAXOFFSETS - 1); ++ for (int i = MAXOFFSETS - 1; i < n; i++) ++ pos += (name[pos] + 1); ++ return (pos); ++ } ++} ++ ++private final void ++setlabels(int labels) { ++ offsets &= ~(0xFF); ++ offsets |= labels; ++} ++ ++private final int ++getlabels() { ++ return (int)(offsets & 0xFF); ++} ++ ++private static final void ++copy(Name src, Name dst) { ++ if (src.offset(0) == 0) { ++ dst.name = src.name; ++ dst.offsets = src.offsets; ++ } else { ++ int offset0 = src.offset(0); ++ int namelen = src.name.length - offset0; ++ int labels = src.labels(); ++ dst.name = new byte[namelen]; ++ System.arraycopy(src.name, offset0, dst.name, 0, namelen); ++ for (int i = 0; i < labels && i < MAXOFFSETS; i++) ++ dst.setoffset(i, src.offset(i) - offset0); ++ dst.setlabels(labels); ++ } ++} ++ ++private final void ++append(byte [] array, int start, int n) throws NameTooLongException { ++ int length = (name == null ? 0 : (name.length - offset(0))); ++ int alength = 0; ++ for (int i = 0, pos = start; i < n; i++) { ++ int len = array[pos]; ++ if (len > MAXLABEL) ++ throw new IllegalStateException("invalid label"); ++ len++; ++ pos += len; ++ alength += len; ++ } ++ int newlength = length + alength; ++ if (newlength > MAXNAME) ++ throw new NameTooLongException(); ++ int labels = getlabels(); ++ int newlabels = labels + n; ++ if (newlabels > MAXLABELS) ++ throw new IllegalStateException("too many labels"); ++ byte [] newname = new byte[newlength]; ++ if (length != 0) ++ System.arraycopy(name, offset(0), newname, 0, length); ++ System.arraycopy(array, start, newname, length, alength); ++ name = newname; ++ for (int i = 0, pos = length; i < n; i++) { ++ setoffset(labels + i, pos); ++ pos += (newname[pos] + 1); ++ } ++ setlabels(newlabels); ++} ++ ++private static TextParseException ++parseException(String str, String message) { ++ return new TextParseException("'" + str + "': " + message); ++} ++ ++private final void ++appendFromString(String fullName, byte [] array, int start, int n) ++throws TextParseException ++{ ++ try { ++ append(array, start, n); ++ } ++ catch (NameTooLongException e) { ++ throw parseException(fullName, "Name too long"); ++ } ++} ++ ++private final void ++appendSafe(byte [] array, int start, int n) { ++ try { ++ append(array, start, n); ++ } ++ catch (NameTooLongException e) { ++ } ++} ++ ++/** ++ * Create a new name from a string and an origin. This does not automatically ++ * make the name absolute; it will be absolute if it has a trailing dot or an ++ * absolute origin is appended. ++ * @param s The string to be converted ++ * @param origin If the name is not absolute, the origin to be appended. ++ * @throws TextParseException The name is invalid. ++ */ ++public ++Name(String s, Name origin) throws TextParseException { ++ if (s.equals("")) ++ throw parseException(s, "empty name"); ++ else if (s.equals("@")) { ++ if (origin == null) ++ copy(empty, this); ++ else ++ copy(origin, this); ++ return; ++ } else if (s.equals(".")) { ++ copy(root, this); ++ return; ++ } ++ int labelstart = -1; ++ int pos = 1; ++ byte [] label = new byte[MAXLABEL + 1]; ++ boolean escaped = false; ++ int digits = 0; ++ int intval = 0; ++ boolean absolute = false; ++ for (int i = 0; i < s.length(); i++) { ++ byte b = (byte) s.charAt(i); ++ if (escaped) { ++ if (b >= '0' && b <= '9' && digits < 3) { ++ digits++; ++ intval *= 10; ++ intval += (b - '0'); ++ if (intval > 255) ++ throw parseException(s, "bad escape"); ++ if (digits < 3) ++ continue; ++ b = (byte) intval; ++ } ++ else if (digits > 0 && digits < 3) ++ throw parseException(s, "bad escape"); ++ if (pos > MAXLABEL) ++ throw parseException(s, "label too long"); ++ labelstart = pos; ++ label[pos++] = b; ++ escaped = false; ++ } else if (b == '\\') { ++ escaped = true; ++ digits = 0; ++ intval = 0; ++ } else if (b == '.') { ++ if (labelstart == -1) ++ throw parseException(s, "invalid empty label"); ++ label[0] = (byte)(pos - 1); ++ appendFromString(s, label, 0, 1); ++ labelstart = -1; ++ pos = 1; ++ } else { ++ if (labelstart == -1) ++ labelstart = i; ++ if (pos > MAXLABEL) ++ throw parseException(s, "label too long"); ++ label[pos++] = b; ++ } ++ } ++ if (digits > 0 && digits < 3) ++ throw parseException(s, "bad escape"); ++ if (escaped) ++ throw parseException(s, "bad escape"); ++ if (labelstart == -1) { ++ appendFromString(s, emptyLabel, 0, 1); ++ absolute = true; ++ } else { ++ label[0] = (byte)(pos - 1); ++ appendFromString(s, label, 0, 1); ++ } ++ if (origin != null && !absolute) ++ appendFromString(s, origin.name, 0, origin.getlabels()); ++} ++ ++/** ++ * Create a new name from a string. This does not automatically make the name ++ * absolute; it will be absolute if it has a trailing dot. ++ * @param s The string to be converted ++ * @throws TextParseException The name is invalid. ++ */ ++public ++Name(String s) throws TextParseException { ++ this(s, null); ++} ++ ++/** ++ * Create a new name from a string and an origin. This does not automatically ++ * make the name absolute; it will be absolute if it has a trailing dot or an ++ * absolute origin is appended. This is identical to the constructor, except ++ * that it will avoid creating new objects in some cases. ++ * @param s The string to be converted ++ * @param origin If the name is not absolute, the origin to be appended. ++ * @throws TextParseException The name is invalid. ++ */ ++public static Name ++fromString(String s, Name origin) throws TextParseException { ++ if (s.equals("@") && origin != null) ++ return origin; ++ else if (s.equals(".")) ++ return (root); ++ ++ return new Name(s, origin); ++} ++ ++/** ++ * Create a new name from a string. This does not automatically make the name ++ * absolute; it will be absolute if it has a trailing dot. This is identical ++ * to the constructor, except that it will avoid creating new objects in some ++ * cases. ++ * @param s The string to be converted ++ * @throws TextParseException The name is invalid. ++ */ ++public static Name ++fromString(String s) throws TextParseException { ++ return fromString(s, null); ++} ++ ++/** ++ * Create a new name from a constant string. This should only be used when ++ the name is known to be good - that is, when it is constant. ++ * @param s The string to be converted ++ * @throws IllegalArgumentException The name is invalid. ++ */ ++public static Name ++fromConstantString(String s) { ++ try { ++ return fromString(s, null); ++ } ++ catch (TextParseException e) { ++ throw new IllegalArgumentException("Invalid name '" + s + "'"); ++ } ++} ++ ++/** ++ * Create a new name from DNS a wire format message ++ * @param in A stream containing the DNS message which is currently ++ * positioned at the start of the name to be read. ++ */ ++public ++Name(DNSInput in) throws WireParseException { ++ int len, pos; ++ boolean done = false; ++ byte [] label = new byte[MAXLABEL + 1]; ++ boolean savedState = false; ++ ++ while (!done) { ++ len = in.readU8(); ++ switch (len & LABEL_MASK) { ++ case LABEL_NORMAL: ++ if (getlabels() >= MAXLABELS) ++ throw new WireParseException("too many labels"); ++ if (len == 0) { ++ append(emptyLabel, 0, 1); ++ done = true; ++ } else { ++ label[0] = (byte)len; ++ in.readByteArray(label, 1, len); ++ append(label, 0, 1); ++ } ++ break; ++ case LABEL_COMPRESSION: ++ pos = in.readU8(); ++ pos += ((len & ~LABEL_MASK) << 8); ++ if (Options.check("verbosecompression")) ++ System.err.println("currently " + in.current() + ++ ", pointer to " + pos); ++ ++ if (pos >= in.current() - 2) ++ throw new WireParseException("bad compression"); ++ if (!savedState) { ++ in.save(); ++ savedState = true; ++ } ++ in.jump(pos); ++ if (Options.check("verbosecompression")) ++ System.err.println("current name '" + this + ++ "', seeking to " + pos); ++ break; ++ default: ++ throw new WireParseException("bad label type"); ++ } ++ } ++ if (savedState) { ++ in.restore(); ++ } ++} ++ ++/** ++ * Create a new name from DNS wire format ++ * @param b A byte array containing the wire format of the name. ++ */ ++public ++Name(byte [] b) throws IOException { ++ this(new DNSInput(b)); ++} ++ ++/** ++ * Create a new name by removing labels from the beginning of an existing Name ++ * @param src An existing Name ++ * @param n The number of labels to remove from the beginning in the copy ++ */ ++public ++Name(Name src, int n) { ++ int slabels = src.labels(); ++ if (n > slabels) ++ throw new IllegalArgumentException("attempted to remove too " + ++ "many labels"); ++ name = src.name; ++ setlabels(slabels - n); ++ for (int i = 0; i < MAXOFFSETS && i < slabels - n; i++) ++ setoffset(i, src.offset(i + n)); ++} ++ ++/** ++ * Creates a new name by concatenating two existing names. ++ * @param prefix The prefix name. ++ * @param suffix The suffix name. ++ * @return The concatenated name. ++ * @throws NameTooLongException The name is too long. ++ */ ++public static Name ++concatenate(Name prefix, Name suffix) throws NameTooLongException { ++ if (prefix.isAbsolute()) ++ return (prefix); ++ Name newname = new Name(); ++ copy(prefix, newname); ++ newname.append(suffix.name, suffix.offset(0), suffix.getlabels()); ++ return newname; ++} ++ ++/** ++ * If this name is a subdomain of origin, return a new name relative to ++ * origin with the same value. Otherwise, return the existing name. ++ * @param origin The origin to remove. ++ * @return The possibly relativized name. ++ */ ++public Name ++relativize(Name origin) { ++ if (origin == null || !subdomain(origin)) ++ return this; ++ Name newname = new Name(); ++ copy(this, newname); ++ int length = length() - origin.length(); ++ int labels = newname.labels() - origin.labels(); ++ newname.setlabels(labels); ++ newname.name = new byte[length]; ++ System.arraycopy(name, offset(0), newname.name, 0, length); ++ return newname; ++} ++ ++/** ++ * Generates a new Name with the first n labels replaced by a wildcard ++ * @return The wildcard name ++ */ ++public Name ++wild(int n) { ++ if (n < 1) ++ throw new IllegalArgumentException("must replace 1 or more " + ++ "labels"); ++ try { ++ Name newname = new Name(); ++ copy(wild, newname); ++ newname.append(name, offset(n), getlabels() - n); ++ return newname; ++ } ++ catch (NameTooLongException e) { ++ throw new IllegalStateException ++ ("Name.wild: concatenate failed"); ++ } ++} ++ ++/** ++ * Generates a new Name to be used when following a DNAME. ++ * @param dname The DNAME record to follow. ++ * @return The constructed name. ++ * @throws NameTooLongException The resulting name is too long. ++ */ ++public Name ++fromDNAME(DNAMERecord dname) throws NameTooLongException { ++ Name dnameowner = dname.getName(); ++ Name dnametarget = dname.getTarget(); ++ if (!subdomain(dnameowner)) ++ return null; ++ ++ int plabels = labels() - dnameowner.labels(); ++ int plength = length() - dnameowner.length(); ++ int pstart = offset(0); ++ ++ int dlabels = dnametarget.labels(); ++ int dlength = dnametarget.length(); ++ ++ if (plength + dlength > MAXNAME) ++ throw new NameTooLongException(); ++ ++ Name newname = new Name(); ++ newname.setlabels(plabels + dlabels); ++ newname.name = new byte[plength + dlength]; ++ System.arraycopy(name, pstart, newname.name, 0, plength); ++ System.arraycopy(dnametarget.name, 0, newname.name, plength, dlength); ++ ++ for (int i = 0, pos = 0; i < MAXOFFSETS && i < plabels + dlabels; i++) { ++ newname.setoffset(i, pos); ++ pos += (newname.name[pos] + 1); ++ } ++ return newname; ++} ++ ++/** ++ * Is this name a wildcard? ++ */ ++public boolean ++isWild() { ++ if (labels() == 0) ++ return false; ++ return (name[0] == (byte)1 && name[1] == (byte)'*'); ++} ++ ++/** ++ * Is this name absolute? ++ */ ++public boolean ++isAbsolute() { ++ if (labels() == 0) ++ return false; ++ return (name[name.length - 1] == 0); ++} ++ ++/** ++ * The length of the name. ++ */ ++public short ++length() { ++ if (getlabels() == 0) ++ return 0; ++ return (short)(name.length - offset(0)); ++} ++ ++/** ++ * The number of labels in the name. ++ */ ++public int ++labels() { ++ return getlabels(); ++} ++ ++/** ++ * Is the current Name a subdomain of the specified name? ++ */ ++public boolean ++subdomain(Name domain) { ++ int labels = labels(); ++ int dlabels = domain.labels(); ++ if (dlabels > labels) ++ return false; ++ if (dlabels == labels) ++ return equals(domain); ++ return domain.equals(name, offset(labels - dlabels)); ++} ++ ++private String ++byteString(byte [] array, int pos) { ++ StringBuffer sb = new StringBuffer(); ++ int len = array[pos++]; ++ for (int i = pos; i < pos + len; i++) { ++ int b = array[i] & 0xFF; ++ if (b <= 0x20 || b >= 0x7f) { ++ sb.append('\\'); ++ sb.append(byteFormat.format(b)); ++ } ++ else if (b == '"' || b == '(' || b == ')' || b == '.' || ++ b == ';' || b == '\\' || b == '@' || b == '$') ++ { ++ sb.append('\\'); ++ sb.append((char)b); ++ } ++ else ++ sb.append((char)b); ++ } ++ return sb.toString(); ++} ++ ++/** ++ * Convert a Name to a String ++ * @return The representation of this name as a (printable) String. ++ */ ++public String ++toString() { ++ int labels = labels(); ++ if (labels == 0) ++ return "@"; ++ else if (labels == 1 && name[offset(0)] == 0) ++ return "."; ++ StringBuffer sb = new StringBuffer(); ++ for (int i = 0, pos = offset(0); i < labels; i++) { ++ int len = name[pos]; ++ if (len > MAXLABEL) ++ throw new IllegalStateException("invalid label"); ++ if (len == 0) ++ break; ++ sb.append(byteString(name, pos)); ++ sb.append('.'); ++ pos += (1 + len); ++ } ++ if (!isAbsolute()) ++ sb.deleteCharAt(sb.length() - 1); ++ return sb.toString(); ++} ++ ++/** ++ * Retrieve the nth label of a Name. This makes a copy of the label; changing ++ * this does not change the Name. ++ * @param n The label to be retrieved. The first label is 0. ++ */ ++public byte [] ++getLabel(int n) { ++ int pos = offset(n); ++ byte len = (byte)(name[pos] + 1); ++ byte [] label = new byte[len]; ++ System.arraycopy(name, pos, label, 0, len); ++ return label; ++} ++ ++/** ++ * Convert the nth label in a Name to a String ++ * @param n The label to be converted to a (printable) String. The first ++ * label is 0. ++ */ ++public String ++getLabelString(int n) { ++ int pos = offset(n); ++ return byteString(name, pos); ++} ++ ++/** ++ * Emit a Name in DNS wire format ++ * @param out The output stream containing the DNS message. ++ * @param c The compression context, or null of no compression is desired. ++ * @throws IllegalArgumentException The name is not absolute. ++ */ ++public void ++toWire(DNSOutput out, Compression c) { ++ if (!isAbsolute()) ++ throw new IllegalArgumentException("toWire() called on " + ++ "non-absolute name"); ++ ++ int labels = labels(); ++ for (int i = 0; i < labels - 1; i++) { ++ Name tname; ++ if (i == 0) ++ tname = this; ++ else ++ tname = new Name(this, i); ++ int pos = -1; ++ if (c != null) ++ pos = c.get(tname); ++ if (pos >= 0) { ++ pos |= (LABEL_MASK << 8); ++ out.writeU16(pos); ++ return; ++ } else { ++ if (c != null) ++ c.add(out.current(), tname); ++ int off = offset(i); ++ out.writeByteArray(name, off, name[off] + 1); ++ } ++ } ++ out.writeU8(0); ++} ++ ++/** ++ * Emit a Name in DNS wire format ++ * @throws IllegalArgumentException The name is not absolute. ++ */ ++public byte [] ++toWire() { ++ DNSOutput out = new DNSOutput(); ++ toWire(out, null); ++ return out.toByteArray(); ++} ++ ++/** ++ * Emit a Name in canonical DNS wire format (all lowercase) ++ * @param out The output stream to which the message is written. ++ */ ++public void ++toWireCanonical(DNSOutput out) { ++ byte [] b = toWireCanonical(); ++ out.writeByteArray(b); ++} ++ ++/** ++ * Emit a Name in canonical DNS wire format (all lowercase) ++ * @return The canonical form of the name. ++ */ ++public byte [] ++toWireCanonical() { ++ int labels = labels(); ++ if (labels == 0) ++ return (new byte[0]); ++ byte [] b = new byte[name.length - offset(0)]; ++ for (int i = 0, spos = offset(0), dpos = 0; i < labels; i++) { ++ int len = name[spos]; ++ if (len > MAXLABEL) ++ throw new IllegalStateException("invalid label"); ++ b[dpos++] = name[spos++]; ++ for (int j = 0; j < len; j++) ++ b[dpos++] = lowercase[(name[spos++] & 0xFF)]; ++ } ++ return b; ++} ++ ++/** ++ * Emit a Name in DNS wire format ++ * @param out The output stream containing the DNS message. ++ * @param c The compression context, or null of no compression is desired. ++ * @param canonical If true, emit the name in canonicalized form ++ * (all lowercase). ++ * @throws IllegalArgumentException The name is not absolute. ++ */ ++public void ++toWire(DNSOutput out, Compression c, boolean canonical) { ++ if (canonical) ++ toWireCanonical(out); ++ else ++ toWire(out, c); ++} ++ ++private final boolean ++equals(byte [] b, int bpos) { ++ int labels = labels(); ++ for (int i = 0, pos = offset(0); i < labels; i++) { ++ if (name[pos] != b[bpos]) ++ return false; ++ int len = name[pos++]; ++ bpos++; ++ if (len > MAXLABEL) ++ throw new IllegalStateException("invalid label"); ++ for (int j = 0; j < len; j++) ++ if (lowercase[(name[pos++] & 0xFF)] != ++ lowercase[(b[bpos++] & 0xFF)]) ++ return false; ++ } ++ return true; ++} ++ ++/** ++ * Are these two Names equivalent? ++ */ ++public boolean ++equals(Object arg) { ++ if (arg == this) ++ return true; ++ if (arg == null || !(arg instanceof Name)) ++ return false; ++ Name d = (Name) arg; ++ if (d.hashcode == 0) ++ d.hashCode(); ++ if (hashcode == 0) ++ hashCode(); ++ if (d.hashcode != hashcode) ++ return false; ++ if (d.labels() != labels()) ++ return false; ++ return equals(d.name, d.offset(0)); ++} ++ ++/** ++ * Computes a hashcode based on the value ++ */ ++public int ++hashCode() { ++ if (hashcode != 0) ++ return (hashcode); ++ int code = 0; ++ for (int i = offset(0); i < name.length; i++) ++ code += ((code << 3) + lowercase[(name[i] & 0xFF)]); ++ hashcode = code; ++ return hashcode; ++} ++ ++/** ++ * Compares this Name to another Object. ++ * @param o The Object to be compared. ++ * @return The value 0 if the argument is a name equivalent to this name; ++ * a value less than 0 if the argument is less than this name in the canonical ++ * ordering, and a value greater than 0 if the argument is greater than this ++ * name in the canonical ordering. ++ * @throws ClassCastException if the argument is not a Name. ++ */ ++public int ++compareTo(Object o) { ++ Name arg = (Name) o; ++ ++ if (this == arg) ++ return (0); ++ ++ int labels = labels(); ++ int alabels = arg.labels(); ++ int compares = labels > alabels ? alabels : labels; ++ ++ for (int i = 1; i <= compares; i++) { ++ int start = offset(labels - i); ++ int astart = arg.offset(alabels - i); ++ int length = name[start]; ++ int alength = arg.name[astart]; ++ for (int j = 0; j < length && j < alength; j++) { ++ int n = lowercase[(name[j + start + 1]) & 0xFF] - ++ lowercase[(arg.name[j + astart + 1]) & 0xFF]; ++ if (n != 0) ++ return (n); ++ } ++ if (length != alength) ++ return (length - alength); ++ } ++ return (labels - alabels); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NameTooLongException.java b/external/asmack/build/src/trunk/org/xbill/DNS/NameTooLongException.java +new file mode 100644 +index 0000000..114be39 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/NameTooLongException.java +@@ -0,0 +1,24 @@ ++// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * An exception thrown when a name is longer than the maximum length of a DNS ++ * name. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class NameTooLongException extends WireParseException { ++ ++public ++NameTooLongException() { ++ super(); ++} ++ ++public ++NameTooLongException(String s) { ++ super(s); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/OPTRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/OPTRecord.java +new file mode 100644 +index 0000000..47fef2f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/OPTRecord.java +@@ -0,0 +1,191 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.util.*; ++ ++/** ++ * Options - describes Extended DNS (EDNS) properties of a Message. ++ * No specific options are defined other than those specified in the ++ * header. An OPT should be generated by Resolver. ++ * ++ * EDNS is a method to extend the DNS protocol while providing backwards ++ * compatibility and not significantly changing the protocol. This ++ * implementation of EDNS is mostly complete at level 0. ++ * ++ * @see Message ++ * @see Resolver ++ * ++ * @author Brian Wellington ++ */ ++ ++public class OPTRecord extends Record { ++ ++private static final long serialVersionUID = -6254521894809367938L; ++ ++private List options; ++ ++OPTRecord() {} ++ ++Record ++getObject() { ++ return new OPTRecord(); ++} ++ ++/** ++ * Creates an OPT Record. This is normally called by SimpleResolver, but can ++ * also be called by a server. ++ * @param payloadSize The size of a packet that can be reassembled on the ++ * sending host. ++ * @param xrcode The value of the extended rcode field. This is the upper ++ * 16 bits of the full rcode. ++ * @param flags Additional message flags. ++ * @param version The EDNS version that this DNS implementation supports. ++ * This should be 0 for dnsjava. ++ * @param options The list of options that comprise the data field. There ++ * are currently no defined options. ++ * @see ExtendedFlags ++ */ ++public ++OPTRecord(int payloadSize, int xrcode, int version, int flags, List options) { ++ super(Name.root, Type.OPT, payloadSize, 0); ++ checkU16("payloadSize", payloadSize); ++ checkU8("xrcode", xrcode); ++ checkU8("version", version); ++ checkU16("flags", flags); ++ ttl = ((long)xrcode << 24) + ((long)version << 16) + flags; ++ if (options != null) { ++ this.options = new ArrayList(options); ++ } ++} ++ ++/** ++ * Creates an OPT Record with no data. This is normally called by ++ * SimpleResolver, but can also be called by a server. ++ * @param payloadSize The size of a packet that can be reassembled on the ++ * sending host. ++ * @param xrcode The value of the extended rcode field. This is the upper ++ * 16 bits of the full rcode. ++ * @param flags Additional message flags. ++ * @param version The EDNS version that this DNS implementation supports. ++ * This should be 0 for dnsjava. ++ * @see ExtendedFlags ++ */ ++public ++OPTRecord(int payloadSize, int xrcode, int version, int flags) { ++ this(payloadSize, xrcode, version, flags, null); ++} ++ ++/** ++ * Creates an OPT Record with no data. This is normally called by ++ * SimpleResolver, but can also be called by a server. ++ */ ++public ++OPTRecord(int payloadSize, int xrcode, int version) { ++ this(payloadSize, xrcode, version, 0, null); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ if (in.remaining() > 0) ++ options = new ArrayList(); ++ while (in.remaining() > 0) { ++ EDNSOption option = EDNSOption.fromWire(in); ++ options.add(option); ++ } ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ throw st.exception("no text format defined for OPT"); ++} ++ ++/** Converts rdata to a String */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ if (options != null) { ++ sb.append(options); ++ sb.append(" "); ++ } ++ sb.append(" ; payload "); ++ sb.append(getPayloadSize()); ++ sb.append(", xrcode "); ++ sb.append(getExtendedRcode()); ++ sb.append(", version "); ++ sb.append(getVersion()); ++ sb.append(", flags "); ++ sb.append(getFlags()); ++ return sb.toString(); ++} ++ ++/** Returns the maximum allowed payload size. */ ++public int ++getPayloadSize() { ++ return dclass; ++} ++ ++/** ++ * Returns the extended Rcode ++ * @see Rcode ++ */ ++public int ++getExtendedRcode() { ++ return (int)(ttl >>> 24); ++} ++ ++/** Returns the highest supported EDNS version */ ++public int ++getVersion() { ++ return (int)((ttl >>> 16) & 0xFF); ++} ++ ++/** Returns the EDNS flags */ ++public int ++getFlags() { ++ return (int)(ttl & 0xFFFF); ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ if (options == null) ++ return; ++ Iterator it = options.iterator(); ++ while (it.hasNext()) { ++ EDNSOption option = (EDNSOption) it.next(); ++ option.toWire(out); ++ } ++} ++ ++/** ++ * Gets all options in the OPTRecord. This returns a list of EDNSOptions. ++ */ ++public List ++getOptions() { ++ if (options == null) ++ return Collections.EMPTY_LIST; ++ return Collections.unmodifiableList(options); ++} ++ ++/** ++ * Gets all options in the OPTRecord with a specific code. This returns a list ++ * of EDNSOptions. ++ */ ++public List ++getOptions(int code) { ++ if (options == null) ++ return Collections.EMPTY_LIST; ++ List list = Collections.EMPTY_LIST; ++ for (Iterator it = options.iterator(); it.hasNext(); ) { ++ EDNSOption opt = (EDNSOption) it.next(); ++ if (opt.getCode() == code) { ++ if (list == Collections.EMPTY_LIST) ++ list = new ArrayList(); ++ list.add(opt); ++ } ++ } ++ return list; ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Opcode.java b/external/asmack/build/src/trunk/org/xbill/DNS/Opcode.java +new file mode 100644 +index 0000000..dadbca1 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Opcode.java +@@ -0,0 +1,60 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Constants and functions relating to DNS opcodes ++ * ++ * @author Brian Wellington ++ */ ++ ++public final class Opcode { ++ ++/** A standard query */ ++public static final int QUERY = 0; ++ ++/** An inverse query (deprecated) */ ++public static final int IQUERY = 1; ++ ++/** A server status request (not used) */ ++public static final int STATUS = 2; ++ ++/** ++ * A message from a primary to a secondary server to initiate a zone transfer ++ */ ++public static final int NOTIFY = 4; ++ ++/** A dynamic update message */ ++public static final int UPDATE = 5; ++ ++private static Mnemonic opcodes = new Mnemonic("DNS Opcode", ++ Mnemonic.CASE_UPPER); ++ ++static { ++ opcodes.setMaximum(0xF); ++ opcodes.setPrefix("RESERVED"); ++ opcodes.setNumericAllowed(true); ++ ++ opcodes.add(QUERY, "QUERY"); ++ opcodes.add(IQUERY, "IQUERY"); ++ opcodes.add(STATUS, "STATUS"); ++ opcodes.add(NOTIFY, "NOTIFY"); ++ opcodes.add(UPDATE, "UPDATE"); ++} ++ ++private ++Opcode() {} ++ ++/** Converts a numeric Opcode into a String */ ++public static String ++string(int i) { ++ return opcodes.getText(i); ++} ++ ++/** Converts a String representation of an Opcode into its numeric value */ ++public static int ++value(String s) { ++ return opcodes.getValue(s); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Options.java b/external/asmack/build/src/trunk/org/xbill/DNS/Options.java +new file mode 100644 +index 0000000..2f1dae3 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Options.java +@@ -0,0 +1,123 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.util.*; ++ ++/** ++ * Boolean options:
      ++ * bindttl - Print TTLs in BIND format
      ++ * multiline - Print records in multiline format
      ++ * noprintin - Don't print the class of a record if it's IN
      ++ * verbose - Turn on general debugging statements
      ++ * verbosemsg - Print all messages sent or received by SimpleResolver
      ++ * verbosecompression - Print messages related to name compression
      ++ * verbosesec - Print messages related to signature verification
      ++ * verbosecache - Print messages related to cache lookups
      ++ *
      ++ * Valued options:
      ++ * tsigfudge=n - Sets the default TSIG fudge value (in seconds)
      ++ * sig0validity=n - Sets the default SIG(0) validity period (in seconds)
      ++ * ++ * @author Brian Wellington ++ */ ++ ++public final class Options { ++ ++private static Map table; ++ ++static { ++ try { ++ refresh(); ++ } ++ catch (SecurityException e) { ++ } ++} ++ ++private ++Options() {} ++ ++public static void ++refresh() { ++ String s = System.getProperty("dnsjava.options"); ++ if (s != null) { ++ StringTokenizer st = new StringTokenizer(s, ","); ++ while (st.hasMoreTokens()) { ++ String token = st.nextToken(); ++ int index = token.indexOf('='); ++ if (index == -1) ++ set(token); ++ else { ++ String option = token.substring(0, index); ++ String value = token.substring(index + 1); ++ set(option, value); ++ } ++ } ++ } ++} ++ ++/** Clears all defined options */ ++public static void ++clear() { ++ table = null; ++} ++ ++/** Sets an option to "true" */ ++public static void ++set(String option) { ++ if (table == null) ++ table = new HashMap(); ++ table.put(option.toLowerCase(), "true"); ++} ++ ++/** Sets an option to the the supplied value */ ++public static void ++set(String option, String value) { ++ if (table == null) ++ table = new HashMap(); ++ table.put(option.toLowerCase(), value.toLowerCase()); ++} ++ ++/** Removes an option */ ++public static void ++unset(String option) { ++ if (table == null) ++ return; ++ table.remove(option.toLowerCase()); ++} ++ ++/** Checks if an option is defined */ ++public static boolean ++check(String option) { ++ if (table == null) ++ return false; ++ return (table.get(option.toLowerCase()) != null); ++} ++ ++/** Returns the value of an option */ ++public static String ++value(String option) { ++ if (table == null) ++ return null; ++ return ((String)table.get(option.toLowerCase())); ++} ++ ++/** ++ * Returns the value of an option as an integer, or -1 if not defined. ++ */ ++public static int ++intValue(String option) { ++ String s = value(option); ++ if (s != null) { ++ try { ++ int val = Integer.parseInt(s); ++ if (val > 0) ++ return (val); ++ } ++ catch (NumberFormatException e) { ++ } ++ } ++ return (-1); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/PTRRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/PTRRecord.java +new file mode 100644 +index 0000000..89be578 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/PTRRecord.java +@@ -0,0 +1,38 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Pointer Record - maps a domain name representing an Internet Address to ++ * a hostname. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class PTRRecord extends SingleCompressedNameBase { ++ ++private static final long serialVersionUID = -8321636610425434192L; ++ ++PTRRecord() {} ++ ++Record ++getObject() { ++ return new PTRRecord(); ++} ++ ++/** ++ * Creates a new PTR Record with the given data ++ * @param target The name of the machine with this address ++ */ ++public ++PTRRecord(Name name, int dclass, long ttl, Name target) { ++ super(name, Type.PTR, dclass, ttl, target, "target"); ++} ++ ++/** Gets the target of the PTR Record */ ++public Name ++getTarget() { ++ return getSingleName(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/PXRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/PXRecord.java +new file mode 100644 +index 0000000..a407241 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/PXRecord.java +@@ -0,0 +1,96 @@ ++// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++/** ++ * X.400 mail mapping record. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class PXRecord extends Record { ++ ++private static final long serialVersionUID = 1811540008806660667L; ++ ++private int preference; ++private Name map822; ++private Name mapX400; ++ ++PXRecord() {} ++ ++Record ++getObject() { ++ return new PXRecord(); ++} ++ ++/** ++ * Creates an PX Record from the given data ++ * @param preference The preference of this mail address. ++ * @param map822 The RFC 822 component of the mail address. ++ * @param mapX400 The X.400 component of the mail address. ++ */ ++public ++PXRecord(Name name, int dclass, long ttl, int preference, ++ Name map822, Name mapX400) ++{ ++ super(name, Type.PX, dclass, ttl); ++ ++ this.preference = checkU16("preference", preference); ++ this.map822 = checkName("map822", map822); ++ this.mapX400 = checkName("mapX400", mapX400); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ preference = in.readU16(); ++ map822 = new Name(in); ++ mapX400 = new Name(in); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ preference = st.getUInt16(); ++ map822 = st.getName(origin); ++ mapX400 = st.getName(origin); ++} ++ ++/** Converts the PX Record to a String */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(preference); ++ sb.append(" "); ++ sb.append(map822); ++ sb.append(" "); ++ sb.append(mapX400); ++ return sb.toString(); ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeU16(preference); ++ map822.toWire(out, null, canonical); ++ mapX400.toWire(out, null, canonical); ++} ++ ++/** Gets the preference of the route. */ ++public int ++getPreference() { ++ return preference; ++} ++ ++/** Gets the RFC 822 component of the mail address. */ ++public Name ++getMap822() { ++ return map822; ++} ++ ++/** Gets the X.400 component of the mail address. */ ++public Name ++getMapX400() { ++ return mapX400; ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/RPRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/RPRecord.java +new file mode 100644 +index 0000000..7aa066c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/RPRecord.java +@@ -0,0 +1,82 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++/** ++ * Responsible Person Record - lists the mail address of a responsible person ++ * and a domain where TXT records are available. ++ * ++ * @author Tom Scola ++ * @author Brian Wellington ++ */ ++ ++public class RPRecord extends Record { ++ ++private static final long serialVersionUID = 8124584364211337460L; ++ ++private Name mailbox; ++private Name textDomain; ++ ++RPRecord() {} ++ ++Record ++getObject() { ++ return new RPRecord(); ++} ++ ++/** ++ * Creates an RP Record from the given data ++ * @param mailbox The responsible person ++ * @param textDomain The address where TXT records can be found ++ */ ++public ++RPRecord(Name name, int dclass, long ttl, Name mailbox, Name textDomain) { ++ super(name, Type.RP, dclass, ttl); ++ ++ this.mailbox = checkName("mailbox", mailbox); ++ this.textDomain = checkName("textDomain", textDomain); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ mailbox = new Name(in); ++ textDomain = new Name(in); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ mailbox = st.getName(origin); ++ textDomain = st.getName(origin); ++} ++ ++/** Converts the RP Record to a String */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(mailbox); ++ sb.append(" "); ++ sb.append(textDomain); ++ return sb.toString(); ++} ++ ++/** Gets the mailbox address of the RP Record */ ++public Name ++getMailbox() { ++ return mailbox; ++} ++ ++/** Gets the text domain info of the RP Record */ ++public Name ++getTextDomain() { ++ return textDomain; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ mailbox.toWire(out, null, canonical); ++ textDomain.toWire(out, null, canonical); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/RRSIGRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/RRSIGRecord.java +new file mode 100644 +index 0000000..c092839 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/RRSIGRecord.java +@@ -0,0 +1,50 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.util.*; ++ ++/** ++ * Recource Record Signature - An RRSIG provides the digital signature of an ++ * RRset, so that the data can be authenticated by a DNSSEC-capable resolver. ++ * The signature is generated by a key contained in a DNSKEY Record. ++ * @see RRset ++ * @see DNSSEC ++ * @see KEYRecord ++ * ++ * @author Brian Wellington ++ */ ++ ++public class RRSIGRecord extends SIGBase { ++ ++private static final long serialVersionUID = -2609150673537226317L; ++ ++RRSIGRecord() {} ++ ++Record ++getObject() { ++ return new RRSIGRecord(); ++} ++ ++/** ++ * Creates an RRSIG Record from the given data ++ * @param covered The RRset type covered by this signature ++ * @param alg The cryptographic algorithm of the key that generated the ++ * signature ++ * @param origttl The original TTL of the RRset ++ * @param expire The time at which the signature expires ++ * @param timeSigned The time at which this signature was generated ++ * @param footprint The footprint/key id of the signing key. ++ * @param signer The owner of the signing key ++ * @param signature Binary data representing the signature ++ */ ++public ++RRSIGRecord(Name name, int dclass, long ttl, int covered, int alg, long origttl, ++ Date expire, Date timeSigned, int footprint, Name signer, ++ byte [] signature) ++{ ++ super(name, Type.RRSIG, dclass, ttl, covered, alg, origttl, expire, ++ timeSigned, footprint, signer, signature); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/RRset.java b/external/asmack/build/src/trunk/org/xbill/DNS/RRset.java +new file mode 100644 +index 0000000..fa1a6ad +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/RRset.java +@@ -0,0 +1,258 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.Serializable; ++import java.util.*; ++ ++/** ++ * A set of Records with the same name, type, and class. Also included ++ * are all RRSIG records signing the data records. ++ * @see Record ++ * @see RRSIGRecord ++ * ++ * @author Brian Wellington ++ */ ++ ++public class RRset implements Serializable { ++ ++private static final long serialVersionUID = -3270249290171239695L; ++ ++/* ++ * rrs contains both normal and RRSIG records, with the RRSIG records ++ * at the end. ++ */ ++private List rrs; ++private short nsigs; ++private short position; ++ ++/** Creates an empty RRset */ ++public ++RRset() { ++ rrs = new ArrayList(1); ++ nsigs = 0; ++ position = 0; ++} ++ ++/** Creates an RRset and sets its contents to the specified record */ ++public ++RRset(Record record) { ++ this(); ++ safeAddRR(record); ++} ++ ++/** Creates an RRset with the contents of an existing RRset */ ++public ++RRset(RRset rrset) { ++ synchronized (rrset) { ++ rrs = (List) ((ArrayList)rrset.rrs).clone(); ++ nsigs = rrset.nsigs; ++ position = rrset.position; ++ } ++} ++ ++private void ++safeAddRR(Record r) { ++ if (!(r instanceof RRSIGRecord)) { ++ if (nsigs == 0) ++ rrs.add(r); ++ else ++ rrs.add(rrs.size() - nsigs, r); ++ } else { ++ rrs.add(r); ++ nsigs++; ++ } ++} ++ ++/** Adds a Record to an RRset */ ++public synchronized void ++addRR(Record r) { ++ if (rrs.size() == 0) { ++ safeAddRR(r); ++ return; ++ } ++ Record first = first(); ++ if (!r.sameRRset(first)) ++ throw new IllegalArgumentException("record does not match " + ++ "rrset"); ++ ++ if (r.getTTL() != first.getTTL()) { ++ if (r.getTTL() > first.getTTL()) { ++ r = r.cloneRecord(); ++ r.setTTL(first.getTTL()); ++ } else { ++ for (int i = 0; i < rrs.size(); i++) { ++ Record tmp = (Record) rrs.get(i); ++ tmp = tmp.cloneRecord(); ++ tmp.setTTL(r.getTTL()); ++ rrs.set(i, tmp); ++ } ++ } ++ } ++ ++ if (!rrs.contains(r)) ++ safeAddRR(r); ++} ++ ++/** Deletes a Record from an RRset */ ++public synchronized void ++deleteRR(Record r) { ++ if (rrs.remove(r) && (r instanceof RRSIGRecord)) ++ nsigs--; ++} ++ ++/** Deletes all Records from an RRset */ ++public synchronized void ++clear() { ++ rrs.clear(); ++ position = 0; ++ nsigs = 0; ++} ++ ++private synchronized Iterator ++iterator(boolean data, boolean cycle) { ++ int size, start, total; ++ ++ total = rrs.size(); ++ ++ if (data) ++ size = total - nsigs; ++ else ++ size = nsigs; ++ if (size == 0) ++ return Collections.EMPTY_LIST.iterator(); ++ ++ if (data) { ++ if (!cycle) ++ start = 0; ++ else { ++ if (position >= size) ++ position = 0; ++ start = position++; ++ } ++ } else { ++ start = total - nsigs; ++ } ++ ++ List list = new ArrayList(size); ++ if (data) { ++ list.addAll(rrs.subList(start, size)); ++ if (start != 0) ++ list.addAll(rrs.subList(0, start)); ++ } else { ++ list.addAll(rrs.subList(start, total)); ++ } ++ ++ return list.iterator(); ++} ++ ++/** ++ * Returns an Iterator listing all (data) records. ++ * @param cycle If true, cycle through the records so that each Iterator will ++ * start with a different record. ++ */ ++public synchronized Iterator ++rrs(boolean cycle) { ++ return iterator(true, cycle); ++} ++ ++/** ++ * Returns an Iterator listing all (data) records. This cycles through ++ * the records, so each Iterator will start with a different record. ++ */ ++public synchronized Iterator ++rrs() { ++ return iterator(true, true); ++} ++ ++/** Returns an Iterator listing all signature records */ ++public synchronized Iterator ++sigs() { ++ return iterator(false, false); ++} ++ ++/** Returns the number of (data) records */ ++public synchronized int ++size() { ++ return rrs.size() - nsigs; ++} ++ ++/** ++ * Returns the name of the records ++ * @see Name ++ */ ++public Name ++getName() { ++ return first().getName(); ++} ++ ++/** ++ * Returns the type of the records ++ * @see Type ++ */ ++public int ++getType() { ++ return first().getRRsetType(); ++} ++ ++/** ++ * Returns the class of the records ++ * @see DClass ++ */ ++public int ++getDClass() { ++ return first().getDClass(); ++} ++ ++/** Returns the ttl of the records */ ++public synchronized long ++getTTL() { ++ return first().getTTL(); ++} ++ ++/** ++ * Returns the first record ++ * @throws IllegalStateException if the rrset is empty ++ */ ++public synchronized Record ++first() { ++ if (rrs.size() == 0) ++ throw new IllegalStateException("rrset is empty"); ++ return (Record) rrs.get(0); ++} ++ ++private String ++iteratorToString(Iterator it) { ++ StringBuffer sb = new StringBuffer(); ++ while (it.hasNext()) { ++ Record rr = (Record) it.next(); ++ sb.append("["); ++ sb.append(rr.rdataToString()); ++ sb.append("]"); ++ if (it.hasNext()) ++ sb.append(" "); ++ } ++ return sb.toString(); ++} ++ ++/** Converts the RRset to a String */ ++public String ++toString() { ++ if (rrs == null) ++ return ("{empty}"); ++ StringBuffer sb = new StringBuffer(); ++ sb.append("{ "); ++ sb.append(getName() + " "); ++ sb.append(getTTL() + " "); ++ sb.append(DClass.string(getDClass()) + " "); ++ sb.append(Type.string(getType()) + " "); ++ sb.append(iteratorToString(iterator(true, false))); ++ if (nsigs > 0) { ++ sb.append(" sigs: "); ++ sb.append(iteratorToString(iterator(false, false))); ++ } ++ sb.append(" }"); ++ return sb.toString(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/RTRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/RTRecord.java +new file mode 100644 +index 0000000..549731e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/RTRecord.java +@@ -0,0 +1,48 @@ ++// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Route Through Record - lists a route preference and intermediate host. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class RTRecord extends U16NameBase { ++ ++private static final long serialVersionUID = -3206215651648278098L; ++ ++RTRecord() {} ++ ++Record ++getObject() { ++ return new RTRecord(); ++} ++ ++/** ++ * Creates an RT Record from the given data ++ * @param preference The preference of the route. Smaller numbers indicate ++ * more preferred routes. ++ * @param intermediateHost The domain name of the host to use as a router. ++ */ ++public ++RTRecord(Name name, int dclass, long ttl, int preference, ++ Name intermediateHost) ++{ ++ super(name, Type.RT, dclass, ttl, preference, "preference", ++ intermediateHost, "intermediateHost"); ++} ++ ++/** Gets the preference of the route. */ ++public int ++getPreference() { ++ return getU16Field(); ++} ++ ++/** Gets the host to use as a router. */ ++public Name ++getIntermediateHost() { ++ return getNameField(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Rcode.java b/external/asmack/build/src/trunk/org/xbill/DNS/Rcode.java +new file mode 100644 +index 0000000..7f0dd1f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Rcode.java +@@ -0,0 +1,123 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Constants and functions relating to DNS rcodes (error values) ++ * ++ * @author Brian Wellington ++ */ ++ ++public final class Rcode { ++ ++private static Mnemonic rcodes = new Mnemonic("DNS Rcode", ++ Mnemonic.CASE_UPPER); ++ ++private static Mnemonic tsigrcodes = new Mnemonic("TSIG rcode", ++ Mnemonic.CASE_UPPER); ++ ++/** No error */ ++public static final int NOERROR = 0; ++ ++/** Format error */ ++public static final int FORMERR = 1; ++ ++/** Server failure */ ++public static final int SERVFAIL = 2; ++ ++/** The name does not exist */ ++public static final int NXDOMAIN = 3; ++ ++/** The operation requested is not implemented */ ++public static final int NOTIMP = 4; ++ ++/** Deprecated synonym for NOTIMP. */ ++public static final int NOTIMPL = 4; ++ ++/** The operation was refused by the server */ ++public static final int REFUSED = 5; ++ ++/** The name exists */ ++public static final int YXDOMAIN = 6; ++ ++/** The RRset (name, type) exists */ ++public static final int YXRRSET = 7; ++ ++/** The RRset (name, type) does not exist */ ++public static final int NXRRSET = 8; ++ ++/** The requestor is not authorized to perform this operation */ ++public static final int NOTAUTH = 9; ++ ++/** The zone specified is not a zone */ ++public static final int NOTZONE = 10; ++ ++/* EDNS extended rcodes */ ++/** Unsupported EDNS level */ ++public static final int BADVERS = 16; ++ ++/* TSIG/TKEY only rcodes */ ++/** The signature is invalid (TSIG/TKEY extended error) */ ++public static final int BADSIG = 16; ++ ++/** The key is invalid (TSIG/TKEY extended error) */ ++public static final int BADKEY = 17; ++ ++/** The time is out of range (TSIG/TKEY extended error) */ ++public static final int BADTIME = 18; ++ ++/** The mode is invalid (TKEY extended error) */ ++public static final int BADMODE = 19; ++ ++static { ++ rcodes.setMaximum(0xFFF); ++ rcodes.setPrefix("RESERVED"); ++ rcodes.setNumericAllowed(true); ++ ++ rcodes.add(NOERROR, "NOERROR"); ++ rcodes.add(FORMERR, "FORMERR"); ++ rcodes.add(SERVFAIL, "SERVFAIL"); ++ rcodes.add(NXDOMAIN, "NXDOMAIN"); ++ rcodes.add(NOTIMP, "NOTIMP"); ++ rcodes.addAlias(NOTIMP, "NOTIMPL"); ++ rcodes.add(REFUSED, "REFUSED"); ++ rcodes.add(YXDOMAIN, "YXDOMAIN"); ++ rcodes.add(YXRRSET, "YXRRSET"); ++ rcodes.add(NXRRSET, "NXRRSET"); ++ rcodes.add(NOTAUTH, "NOTAUTH"); ++ rcodes.add(NOTZONE, "NOTZONE"); ++ rcodes.add(BADVERS, "BADVERS"); ++ ++ tsigrcodes.setMaximum(0xFFFF); ++ tsigrcodes.setPrefix("RESERVED"); ++ tsigrcodes.setNumericAllowed(true); ++ tsigrcodes.addAll(rcodes); ++ ++ tsigrcodes.add(BADSIG, "BADSIG"); ++ tsigrcodes.add(BADKEY, "BADKEY"); ++ tsigrcodes.add(BADTIME, "BADTIME"); ++ tsigrcodes.add(BADMODE, "BADMODE"); ++} ++ ++private ++Rcode() {} ++ ++/** Converts a numeric Rcode into a String */ ++public static String ++string(int i) { ++ return rcodes.getText(i); ++} ++ ++/** Converts a numeric TSIG extended Rcode into a String */ ++public static String ++TSIGstring(int i) { ++ return tsigrcodes.getText(i); ++} ++ ++/** Converts a String representation of an Rcode into its numeric value */ ++public static int ++value(String s) { ++ return rcodes.getValue(s); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Record.java b/external/asmack/build/src/trunk/org/xbill/DNS/Record.java +new file mode 100644 +index 0000000..8da7015 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Record.java +@@ -0,0 +1,736 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.text.*; ++import java.util.*; ++import org.xbill.DNS.utils.*; ++ ++/** ++ * A generic DNS resource record. The specific record types extend this class. ++ * A record contains a name, type, class, ttl, and rdata. ++ * ++ * @author Brian Wellington ++ */ ++ ++public abstract class Record implements Cloneable, Comparable, Serializable { ++ ++private static final long serialVersionUID = 2694906050116005466L; ++ ++protected Name name; ++protected int type, dclass; ++protected long ttl; ++ ++private static final DecimalFormat byteFormat = new DecimalFormat(); ++ ++static { ++ byteFormat.setMinimumIntegerDigits(3); ++} ++ ++protected ++Record() {} ++ ++Record(Name name, int type, int dclass, long ttl) { ++ if (!name.isAbsolute()) ++ throw new RelativeNameException(name); ++ Type.check(type); ++ DClass.check(dclass); ++ TTL.check(ttl); ++ this.name = name; ++ this.type = type; ++ this.dclass = dclass; ++ this.ttl = ttl; ++} ++ ++/** ++ * Creates an empty record of the correct type; must be overriden ++ */ ++abstract Record ++getObject(); ++ ++private static final Record ++getEmptyRecord(Name name, int type, int dclass, long ttl, boolean hasData) { ++ Record proto, rec; ++ ++ if (hasData) { ++ proto = Type.getProto(type); ++ if (proto != null) ++ rec = proto.getObject(); ++ else ++ rec = new UNKRecord(); ++ } else ++ rec = new EmptyRecord(); ++ rec.name = name; ++ rec.type = type; ++ rec.dclass = dclass; ++ rec.ttl = ttl; ++ return rec; ++} ++ ++/** ++ * Converts the type-specific RR to wire format - must be overriden ++ */ ++abstract void ++rrFromWire(DNSInput in) throws IOException; ++ ++private static Record ++newRecord(Name name, int type, int dclass, long ttl, int length, DNSInput in) ++throws IOException ++{ ++ Record rec; ++ rec = getEmptyRecord(name, type, dclass, ttl, in != null); ++ if (in != null) { ++ if (in.remaining() < length) ++ throw new WireParseException("truncated record"); ++ in.setActive(length); ++ ++ rec.rrFromWire(in); ++ ++ if (in.remaining() > 0) ++ throw new WireParseException("invalid record length"); ++ in.clearActive(); ++ } ++ return rec; ++} ++ ++/** ++ * Creates a new record, with the given parameters. ++ * @param name The owner name of the record. ++ * @param type The record's type. ++ * @param dclass The record's class. ++ * @param ttl The record's time to live. ++ * @param length The length of the record's data. ++ * @param data The rdata of the record, in uncompressed DNS wire format. Only ++ * the first length bytes are used. ++ */ ++public static Record ++newRecord(Name name, int type, int dclass, long ttl, int length, byte [] data) { ++ if (!name.isAbsolute()) ++ throw new RelativeNameException(name); ++ Type.check(type); ++ DClass.check(dclass); ++ TTL.check(ttl); ++ ++ DNSInput in; ++ if (data != null) ++ in = new DNSInput(data); ++ else ++ in = null; ++ try { ++ return newRecord(name, type, dclass, ttl, length, in); ++ } ++ catch (IOException e) { ++ return null; ++ } ++} ++ ++/** ++ * Creates a new record, with the given parameters. ++ * @param name The owner name of the record. ++ * @param type The record's type. ++ * @param dclass The record's class. ++ * @param ttl The record's time to live. ++ * @param data The complete rdata of the record, in uncompressed DNS wire ++ * format. ++ */ ++public static Record ++newRecord(Name name, int type, int dclass, long ttl, byte [] data) { ++ return newRecord(name, type, dclass, ttl, data.length, data); ++} ++ ++/** ++ * Creates a new empty record, with the given parameters. ++ * @param name The owner name of the record. ++ * @param type The record's type. ++ * @param dclass The record's class. ++ * @param ttl The record's time to live. ++ * @return An object of a subclass of Record ++ */ ++public static Record ++newRecord(Name name, int type, int dclass, long ttl) { ++ if (!name.isAbsolute()) ++ throw new RelativeNameException(name); ++ Type.check(type); ++ DClass.check(dclass); ++ TTL.check(ttl); ++ ++ return getEmptyRecord(name, type, dclass, ttl, false); ++} ++ ++/** ++ * Creates a new empty record, with the given parameters. This method is ++ * designed to create records that will be added to the QUERY section ++ * of a message. ++ * @param name The owner name of the record. ++ * @param type The record's type. ++ * @param dclass The record's class. ++ * @return An object of a subclass of Record ++ */ ++public static Record ++newRecord(Name name, int type, int dclass) { ++ return newRecord(name, type, dclass, 0); ++} ++ ++static Record ++fromWire(DNSInput in, int section, boolean isUpdate) throws IOException { ++ int type, dclass; ++ long ttl; ++ int length; ++ Name name; ++ Record rec; ++ ++ name = new Name(in); ++ type = in.readU16(); ++ dclass = in.readU16(); ++ ++ if (section == Section.QUESTION) ++ return newRecord(name, type, dclass); ++ ++ ttl = in.readU32(); ++ length = in.readU16(); ++ if (length == 0 && isUpdate && ++ (section == Section.PREREQ || section == Section.UPDATE)) ++ return newRecord(name, type, dclass, ttl); ++ rec = newRecord(name, type, dclass, ttl, length, in); ++ return rec; ++} ++ ++static Record ++fromWire(DNSInput in, int section) throws IOException { ++ return fromWire(in, section, false); ++} ++ ++/** ++ * Builds a Record from DNS uncompressed wire format. ++ */ ++public static Record ++fromWire(byte [] b, int section) throws IOException { ++ return fromWire(new DNSInput(b), section, false); ++} ++ ++void ++toWire(DNSOutput out, int section, Compression c) { ++ name.toWire(out, c); ++ out.writeU16(type); ++ out.writeU16(dclass); ++ if (section == Section.QUESTION) ++ return; ++ out.writeU32(ttl); ++ int lengthPosition = out.current(); ++ out.writeU16(0); /* until we know better */ ++ rrToWire(out, c, false); ++ int rrlength = out.current() - lengthPosition - 2; ++ out.writeU16At(rrlength, lengthPosition); ++} ++ ++/** ++ * Converts a Record into DNS uncompressed wire format. ++ */ ++public byte [] ++toWire(int section) { ++ DNSOutput out = new DNSOutput(); ++ toWire(out, section, null); ++ return out.toByteArray(); ++} ++ ++private void ++toWireCanonical(DNSOutput out, boolean noTTL) { ++ name.toWireCanonical(out); ++ out.writeU16(type); ++ out.writeU16(dclass); ++ if (noTTL) { ++ out.writeU32(0); ++ } else { ++ out.writeU32(ttl); ++ } ++ int lengthPosition = out.current(); ++ out.writeU16(0); /* until we know better */ ++ rrToWire(out, null, true); ++ int rrlength = out.current() - lengthPosition - 2; ++ out.writeU16At(rrlength, lengthPosition); ++} ++ ++/* ++ * Converts a Record into canonical DNS uncompressed wire format (all names are ++ * converted to lowercase), optionally ignoring the TTL. ++ */ ++private byte [] ++toWireCanonical(boolean noTTL) { ++ DNSOutput out = new DNSOutput(); ++ toWireCanonical(out, noTTL); ++ return out.toByteArray(); ++} ++ ++/** ++ * Converts a Record into canonical DNS uncompressed wire format (all names are ++ * converted to lowercase). ++ */ ++public byte [] ++toWireCanonical() { ++ return toWireCanonical(false); ++} ++ ++/** ++ * Converts the rdata in a Record into canonical DNS uncompressed wire format ++ * (all names are converted to lowercase). ++ */ ++public byte [] ++rdataToWireCanonical() { ++ DNSOutput out = new DNSOutput(); ++ rrToWire(out, null, true); ++ return out.toByteArray(); ++} ++ ++/** ++ * Converts the type-specific RR to text format - must be overriden ++ */ ++abstract String rrToString(); ++ ++/** ++ * Converts the rdata portion of a Record into a String representation ++ */ ++public String ++rdataToString() { ++ return rrToString(); ++} ++ ++/** ++ * Converts a Record into a String representation ++ */ ++public String ++toString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(name); ++ if (sb.length() < 8) ++ sb.append("\t"); ++ if (sb.length() < 16) ++ sb.append("\t"); ++ sb.append("\t"); ++ if (Options.check("BINDTTL")) ++ sb.append(TTL.format(ttl)); ++ else ++ sb.append(ttl); ++ sb.append("\t"); ++ if (dclass != DClass.IN || !Options.check("noPrintIN")) { ++ sb.append(DClass.string(dclass)); ++ sb.append("\t"); ++ } ++ sb.append(Type.string(type)); ++ String rdata = rrToString(); ++ if (!rdata.equals("")) { ++ sb.append("\t"); ++ sb.append(rdata); ++ } ++ return sb.toString(); ++} ++ ++/** ++ * Converts the text format of an RR to the internal format - must be overriden ++ */ ++abstract void ++rdataFromString(Tokenizer st, Name origin) throws IOException; ++ ++/** ++ * Converts a String into a byte array. ++ */ ++protected static byte [] ++byteArrayFromString(String s) throws TextParseException { ++ byte [] array = s.getBytes(); ++ boolean escaped = false; ++ boolean hasEscapes = false; ++ ++ for (int i = 0; i < array.length; i++) { ++ if (array[i] == '\\') { ++ hasEscapes = true; ++ break; ++ } ++ } ++ if (!hasEscapes) { ++ if (array.length > 255) { ++ throw new TextParseException("text string too long"); ++ } ++ return array; ++ } ++ ++ ByteArrayOutputStream os = new ByteArrayOutputStream(); ++ ++ int digits = 0; ++ int intval = 0; ++ for (int i = 0; i < array.length; i++) { ++ byte b = array[i]; ++ if (escaped) { ++ if (b >= '0' && b <= '9' && digits < 3) { ++ digits++; ++ intval *= 10; ++ intval += (b - '0'); ++ if (intval > 255) ++ throw new TextParseException ++ ("bad escape"); ++ if (digits < 3) ++ continue; ++ b = (byte) intval; ++ } ++ else if (digits > 0 && digits < 3) ++ throw new TextParseException("bad escape"); ++ os.write(b); ++ escaped = false; ++ } ++ else if (array[i] == '\\') { ++ escaped = true; ++ digits = 0; ++ intval = 0; ++ } ++ else ++ os.write(array[i]); ++ } ++ if (digits > 0 && digits < 3) ++ throw new TextParseException("bad escape"); ++ array = os.toByteArray(); ++ if (array.length > 255) { ++ throw new TextParseException("text string too long"); ++ } ++ ++ return os.toByteArray(); ++} ++ ++/** ++ * Converts a byte array into a String. ++ */ ++protected static String ++byteArrayToString(byte [] array, boolean quote) { ++ StringBuffer sb = new StringBuffer(); ++ if (quote) ++ sb.append('"'); ++ for (int i = 0; i < array.length; i++) { ++ int b = array[i] & 0xFF; ++ if (b < 0x20 || b >= 0x7f) { ++ sb.append('\\'); ++ sb.append(byteFormat.format(b)); ++ } else if (b == '"' || b == '\\') { ++ sb.append('\\'); ++ sb.append((char)b); ++ } else ++ sb.append((char)b); ++ } ++ if (quote) ++ sb.append('"'); ++ return sb.toString(); ++} ++ ++/** ++ * Converts a byte array into the unknown RR format. ++ */ ++protected static String ++unknownToString(byte [] data) { ++ StringBuffer sb = new StringBuffer(); ++ sb.append("\\# "); ++ sb.append(data.length); ++ sb.append(" "); ++ sb.append(base16.toString(data)); ++ return sb.toString(); ++} ++ ++/** ++ * Builds a new Record from its textual representation ++ * @param name The owner name of the record. ++ * @param type The record's type. ++ * @param dclass The record's class. ++ * @param ttl The record's time to live. ++ * @param st A tokenizer containing the textual representation of the rdata. ++ * @param origin The default origin to be appended to relative domain names. ++ * @return The new record ++ * @throws IOException The text format was invalid. ++ */ ++public static Record ++fromString(Name name, int type, int dclass, long ttl, Tokenizer st, Name origin) ++throws IOException ++{ ++ Record rec; ++ ++ if (!name.isAbsolute()) ++ throw new RelativeNameException(name); ++ Type.check(type); ++ DClass.check(dclass); ++ TTL.check(ttl); ++ ++ Tokenizer.Token t = st.get(); ++ if (t.type == Tokenizer.IDENTIFIER && t.value.equals("\\#")) { ++ int length = st.getUInt16(); ++ byte [] data = st.getHex(); ++ if (data == null) { ++ data = new byte[0]; ++ } ++ if (length != data.length) ++ throw st.exception("invalid unknown RR encoding: " + ++ "length mismatch"); ++ DNSInput in = new DNSInput(data); ++ return newRecord(name, type, dclass, ttl, length, in); ++ } ++ st.unget(); ++ rec = getEmptyRecord(name, type, dclass, ttl, true); ++ rec.rdataFromString(st, origin); ++ t = st.get(); ++ if (t.type != Tokenizer.EOL && t.type != Tokenizer.EOF) { ++ throw st.exception("unexpected tokens at end of record"); ++ } ++ return rec; ++} ++ ++/** ++ * Builds a new Record from its textual representation ++ * @param name The owner name of the record. ++ * @param type The record's type. ++ * @param dclass The record's class. ++ * @param ttl The record's time to live. ++ * @param s The textual representation of the rdata. ++ * @param origin The default origin to be appended to relative domain names. ++ * @return The new record ++ * @throws IOException The text format was invalid. ++ */ ++public static Record ++fromString(Name name, int type, int dclass, long ttl, String s, Name origin) ++throws IOException ++{ ++ return fromString(name, type, dclass, ttl, new Tokenizer(s), origin); ++} ++ ++/** ++ * Returns the record's name ++ * @see Name ++ */ ++public Name ++getName() { ++ return name; ++} ++ ++/** ++ * Returns the record's type ++ * @see Type ++ */ ++public int ++getType() { ++ return type; ++} ++ ++/** ++ * Returns the type of RRset that this record would belong to. For all types ++ * except RRSIG, this is equivalent to getType(). ++ * @return The type of record, if not RRSIG. If the type is RRSIG, ++ * the type covered is returned. ++ * @see Type ++ * @see RRset ++ * @see SIGRecord ++ */ ++public int ++getRRsetType() { ++ if (type == Type.RRSIG) { ++ RRSIGRecord sig = (RRSIGRecord) this; ++ return sig.getTypeCovered(); ++ } ++ return type; ++} ++ ++/** ++ * Returns the record's class ++ */ ++public int ++getDClass() { ++ return dclass; ++} ++ ++/** ++ * Returns the record's TTL ++ */ ++public long ++getTTL() { ++ return ttl; ++} ++ ++/** ++ * Converts the type-specific RR to wire format - must be overriden ++ */ ++abstract void ++rrToWire(DNSOutput out, Compression c, boolean canonical); ++ ++/** ++ * Determines if two Records could be part of the same RRset. ++ * This compares the name, type, and class of the Records; the ttl and ++ * rdata are not compared. ++ */ ++public boolean ++sameRRset(Record rec) { ++ return (getRRsetType() == rec.getRRsetType() && ++ dclass == rec.dclass && ++ name.equals(rec.name)); ++} ++ ++/** ++ * Determines if two Records are identical. This compares the name, type, ++ * class, and rdata (with names canonicalized). The TTLs are not compared. ++ * @param arg The record to compare to ++ * @return true if the records are equal, false otherwise. ++ */ ++public boolean ++equals(Object arg) { ++ if (arg == null || !(arg instanceof Record)) ++ return false; ++ Record r = (Record) arg; ++ if (type != r.type || dclass != r.dclass || !name.equals(r.name)) ++ return false; ++ byte [] array1 = rdataToWireCanonical(); ++ byte [] array2 = r.rdataToWireCanonical(); ++ return Arrays.equals(array1, array2); ++} ++ ++/** ++ * Generates a hash code based on the Record's data. ++ */ ++public int ++hashCode() { ++ byte [] array = toWireCanonical(true); ++ int code = 0; ++ for (int i = 0; i < array.length; i++) ++ code += ((code << 3) + (array[i] & 0xFF)); ++ return code; ++} ++ ++Record ++cloneRecord() { ++ try { ++ return (Record) clone(); ++ } ++ catch (CloneNotSupportedException e) { ++ throw new IllegalStateException(); ++ } ++} ++ ++/** ++ * Creates a new record identical to the current record, but with a different ++ * name. This is most useful for replacing the name of a wildcard record. ++ */ ++public Record ++withName(Name name) { ++ if (!name.isAbsolute()) ++ throw new RelativeNameException(name); ++ Record rec = cloneRecord(); ++ rec.name = name; ++ return rec; ++} ++ ++/** ++ * Creates a new record identical to the current record, but with a different ++ * class and ttl. This is most useful for dynamic update. ++ */ ++Record ++withDClass(int dclass, long ttl) { ++ Record rec = cloneRecord(); ++ rec.dclass = dclass; ++ rec.ttl = ttl; ++ return rec; ++} ++ ++/* Sets the TTL to the specified value. This is intentionally not public. */ ++void ++setTTL(long ttl) { ++ this.ttl = ttl; ++} ++ ++/** ++ * Compares this Record to another Object. ++ * @param o The Object to be compared. ++ * @return The value 0 if the argument is a record equivalent to this record; ++ * a value less than 0 if the argument is less than this record in the ++ * canonical ordering, and a value greater than 0 if the argument is greater ++ * than this record in the canonical ordering. The canonical ordering ++ * is defined to compare by name, class, type, and rdata. ++ * @throws ClassCastException if the argument is not a Record. ++ */ ++public int ++compareTo(Object o) { ++ Record arg = (Record) o; ++ ++ if (this == arg) ++ return (0); ++ ++ int n = name.compareTo(arg.name); ++ if (n != 0) ++ return (n); ++ n = dclass - arg.dclass; ++ if (n != 0) ++ return (n); ++ n = type - arg.type; ++ if (n != 0) ++ return (n); ++ byte [] rdata1 = rdataToWireCanonical(); ++ byte [] rdata2 = arg.rdataToWireCanonical(); ++ for (int i = 0; i < rdata1.length && i < rdata2.length; i++) { ++ n = (rdata1[i] & 0xFF) - (rdata2[i] & 0xFF); ++ if (n != 0) ++ return (n); ++ } ++ return (rdata1.length - rdata2.length); ++} ++ ++/** ++ * Returns the name for which additional data processing should be done ++ * for this record. This can be used both for building responses and ++ * parsing responses. ++ * @return The name to used for additional data processing, or null if this ++ * record type does not require additional data processing. ++ */ ++public Name ++getAdditionalName() { ++ return null; ++} ++ ++/* Checks that an int contains an unsigned 8 bit value */ ++static int ++checkU8(String field, int val) { ++ if (val < 0 || val > 0xFF) ++ throw new IllegalArgumentException("\"" + field + "\" " + val + ++ " must be an unsigned 8 " + ++ "bit value"); ++ return val; ++} ++ ++/* Checks that an int contains an unsigned 16 bit value */ ++static int ++checkU16(String field, int val) { ++ if (val < 0 || val > 0xFFFF) ++ throw new IllegalArgumentException("\"" + field + "\" " + val + ++ " must be an unsigned 16 " + ++ "bit value"); ++ return val; ++} ++ ++/* Checks that a long contains an unsigned 32 bit value */ ++static long ++checkU32(String field, long val) { ++ if (val < 0 || val > 0xFFFFFFFFL) ++ throw new IllegalArgumentException("\"" + field + "\" " + val + ++ " must be an unsigned 32 " + ++ "bit value"); ++ return val; ++} ++ ++/* Checks that a name is absolute */ ++static Name ++checkName(String field, Name name) { ++ if (!name.isAbsolute()) ++ throw new RelativeNameException(name); ++ return name; ++} ++ ++static byte [] ++checkByteArrayLength(String field, byte [] array, int maxLength) { ++ if (array.length > 0xFFFF) ++ throw new IllegalArgumentException("\"" + field + "\" array " + ++ "must have no more than " + ++ maxLength + " elements"); ++ byte [] out = new byte[array.length]; ++ System.arraycopy(array, 0, out, 0, array.length); ++ return out; ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/RelativeNameException.java b/external/asmack/build/src/trunk/org/xbill/DNS/RelativeNameException.java +new file mode 100644 +index 0000000..869fd39 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/RelativeNameException.java +@@ -0,0 +1,24 @@ ++// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * An exception thrown when a relative name is passed as an argument to ++ * a method requiring an absolute name. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class RelativeNameException extends IllegalArgumentException { ++ ++public ++RelativeNameException(Name name) { ++ super("'" + name + "' is not an absolute name"); ++} ++ ++public ++RelativeNameException(String s) { ++ super(s); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ResolveThread.java b/external/asmack/build/src/trunk/org/xbill/DNS/ResolveThread.java +new file mode 100644 +index 0000000..3087cdb +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/ResolveThread.java +@@ -0,0 +1,45 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * A special-purpose thread used by Resolvers (both SimpleResolver and ++ * ExtendedResolver) to perform asynchronous queries. ++ * ++ * @author Brian Wellington ++ */ ++ ++class ResolveThread extends Thread { ++ ++private Message query; ++private Object id; ++private ResolverListener listener; ++private Resolver res; ++ ++/** Creates a new ResolveThread */ ++public ++ResolveThread(Resolver res, Message query, Object id, ++ ResolverListener listener) ++{ ++ this.res = res; ++ this.query = query; ++ this.id = id; ++ this.listener = listener; ++} ++ ++ ++/** ++ * Performs the query, and executes the callback. ++ */ ++public void ++run() { ++ try { ++ Message response = res.send(query); ++ listener.receiveMessage(id, response); ++ } ++ catch (Exception e) { ++ listener.handleException(id, e); ++ } ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Resolver.java b/external/asmack/build/src/trunk/org/xbill/DNS/Resolver.java +new file mode 100644 +index 0000000..7d28d40 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Resolver.java +@@ -0,0 +1,95 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.util.*; ++ ++/** ++ * Interface describing a resolver. ++ * ++ * @author Brian Wellington ++ */ ++ ++public interface Resolver { ++ ++/** ++ * Sets the port to communicate with on the server ++ * @param port The port to send messages to ++ */ ++void setPort(int port); ++ ++/** ++ * Sets whether TCP connections will be sent by default ++ * @param flag Indicates whether TCP connections are made ++ */ ++void setTCP(boolean flag); ++ ++/** ++ * Sets whether truncated responses will be ignored. If not, a truncated ++ * response over UDP will cause a retransmission over TCP. ++ * @param flag Indicates whether truncated responses should be ignored. ++ */ ++void setIgnoreTruncation(boolean flag); ++ ++/** ++ * Sets the EDNS version used on outgoing messages. ++ * @param level The EDNS level to use. 0 indicates EDNS0 and -1 indicates no ++ * EDNS. ++ * @throws IllegalArgumentException An invalid level was indicated. ++ */ ++void setEDNS(int level); ++ ++/** ++ * Sets the EDNS information on outgoing messages. ++ * @param level The EDNS level to use. 0 indicates EDNS0 and -1 indicates no ++ * EDNS. ++ * @param payloadSize The maximum DNS packet size that this host is capable ++ * of receiving over UDP. If 0 is specified, the default (1280) is used. ++ * @param flags EDNS extended flags to be set in the OPT record. ++ * @param options EDNS options to be set in the OPT record, specified as a ++ * List of OPTRecord.Option elements. ++ * @throws IllegalArgumentException An invalid field was specified. ++ * @see OPTRecord ++ */ ++void setEDNS(int level, int payloadSize, int flags, List options); ++ ++/** ++ * Specifies the TSIG key that messages will be signed with ++ * @param key The key ++ */ ++void setTSIGKey(TSIG key); ++ ++/** ++ * Sets the amount of time to wait for a response before giving up. ++ * @param secs The number of seconds to wait. ++ * @param msecs The number of milliseconds to wait. ++ */ ++void setTimeout(int secs, int msecs); ++ ++/** ++ * Sets the amount of time to wait for a response before giving up. ++ * @param secs The number of seconds to wait. ++ */ ++void setTimeout(int secs); ++ ++/** ++ * Sends a message and waits for a response. ++ * @param query The query to send. ++ * @return The response ++ * @throws IOException An error occurred while sending or receiving. ++ */ ++Message send(Message query) throws IOException; ++ ++/** ++ * Asynchronously sends a message registering a listener to receive a callback ++ * on success or exception. Multiple asynchronous lookups can be performed ++ * in parallel. Since the callback may be invoked before the function returns, ++ * external synchronization is necessary. ++ * @param query The query to send ++ * @param listener The object containing the callbacks. ++ * @return An identifier, which is also a parameter in the callback ++ */ ++Object sendAsync(final Message query, final ResolverListener listener); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ResolverConfig.java b/external/asmack/build/src/trunk/org/xbill/DNS/ResolverConfig.java +new file mode 100644 +index 0000000..7b09daf +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/ResolverConfig.java +@@ -0,0 +1,509 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.lang.reflect.*; ++import java.util.*; ++ ++/** ++ * A class that tries to locate name servers and the search path to ++ * be appended to unqualified names. ++ * ++ * The following are attempted, in order, until one succeeds. ++ *
        ++ *
      • The properties 'dns.server' and 'dns.search' (comma delimited lists) ++ * are checked. The servers can either be IP addresses or hostnames ++ * (which are resolved using Java's built in DNS support). ++ *
      • The sun.net.dns.ResolverConfiguration class is queried. ++ *
      • On Unix, /etc/resolv.conf is parsed. ++ *
      • On Windows, ipconfig/winipcfg is called and its output parsed. This ++ * may fail for non-English versions on Windows. ++ *
      • "localhost" is used as the nameserver, and the search path is empty. ++ *
      ++ * ++ * These routines will be called internally when creating Resolvers/Lookups ++ * without explicitly specifying server names, and can also be called ++ * directly if desired. ++ * ++ * @author Brian Wellington ++ * @author Yannick Meudal ++ * @author Arnt Gulbrandsen ++ */ ++ ++public class ResolverConfig { ++ ++private String [] servers = null; ++private Name [] searchlist = null; ++private int ndots = -1; ++ ++private static ResolverConfig currentConfig; ++ ++static { ++ refresh(); ++} ++ ++public ++ResolverConfig() { ++ if (findProperty()) ++ return; ++ if (findSunJVM()) ++ return; ++ if (servers == null || searchlist == null) { ++ String OS = System.getProperty("os.name"); ++ String vendor = System.getProperty("java.vendor"); ++ if (OS.indexOf("Windows") != -1) { ++ if (OS.indexOf("95") != -1 || ++ OS.indexOf("98") != -1 || ++ OS.indexOf("ME") != -1) ++ find95(); ++ else ++ findNT(); ++ } else if (OS.indexOf("NetWare") != -1) { ++ findNetware(); ++ } else if (vendor.indexOf("Android") != -1) { ++ findAndroid(); ++ } else { ++ findUnix(); ++ } ++ } ++} ++ ++private void ++addServer(String server, List list) { ++ if (list.contains(server)) ++ return; ++ if (Options.check("verbose")) ++ System.out.println("adding server " + server); ++ list.add(server); ++} ++ ++private void ++addSearch(String search, List list) { ++ Name name; ++ if (Options.check("verbose")) ++ System.out.println("adding search " + search); ++ try { ++ name = Name.fromString(search, Name.root); ++ } ++ catch (TextParseException e) { ++ return; ++ } ++ if (list.contains(name)) ++ return; ++ list.add(name); ++} ++ ++private int ++parseNdots(String token) { ++ token = token.substring(6); ++ try { ++ int ndots = Integer.parseInt(token); ++ if (ndots >= 0) { ++ if (Options.check("verbose")) ++ System.out.println("setting ndots " + token); ++ return ndots; ++ } ++ } ++ catch (NumberFormatException e) { ++ } ++ return -1; ++} ++ ++private void ++configureFromLists(List lserver, List lsearch) { ++ if (servers == null && lserver.size() > 0) ++ servers = (String []) lserver.toArray(new String[0]); ++ if (searchlist == null && lsearch.size() > 0) ++ searchlist = (Name []) lsearch.toArray(new Name[0]); ++} ++ ++private void ++configureNdots(int lndots) { ++ if (ndots < 0 && lndots > 0) ++ ndots = lndots; ++} ++ ++/** ++ * Looks in the system properties to find servers and a search path. ++ * Servers are defined by dns.server=server1,server2... ++ * The search path is defined by dns.search=domain1,domain2... ++ */ ++private boolean ++findProperty() { ++ String prop; ++ List lserver = new ArrayList(0); ++ List lsearch = new ArrayList(0); ++ StringTokenizer st; ++ ++ prop = System.getProperty("dns.server"); ++ if (prop != null) { ++ st = new StringTokenizer(prop, ","); ++ while (st.hasMoreTokens()) ++ addServer(st.nextToken(), lserver); ++ } ++ ++ prop = System.getProperty("dns.search"); ++ if (prop != null) { ++ st = new StringTokenizer(prop, ","); ++ while (st.hasMoreTokens()) ++ addSearch(st.nextToken(), lsearch); ++ } ++ configureFromLists(lserver, lsearch); ++ return (servers != null && searchlist != null); ++} ++ ++/** ++ * Uses the undocumented Sun DNS implementation to determine the configuration. ++ * This doesn't work or even compile with all JVMs (gcj, for example). ++ */ ++private boolean ++findSunJVM() { ++ List lserver = new ArrayList(0); ++ List lserver_tmp; ++ List lsearch = new ArrayList(0); ++ List lsearch_tmp; ++ ++ try { ++ Class [] noClasses = new Class[0]; ++ Object [] noObjects = new Object[0]; ++ String resConfName = "sun.net.dns.ResolverConfiguration"; ++ Class resConfClass = Class.forName(resConfName); ++ Object resConf; ++ ++ // ResolverConfiguration resConf = ResolverConfiguration.open(); ++ Method open = resConfClass.getDeclaredMethod("open", noClasses); ++ resConf = open.invoke(null, noObjects); ++ ++ // lserver_tmp = resConf.nameservers(); ++ Method nameservers = resConfClass.getMethod("nameservers", ++ noClasses); ++ lserver_tmp = (List) nameservers.invoke(resConf, noObjects); ++ ++ // lsearch_tmp = resConf.searchlist(); ++ Method searchlist = resConfClass.getMethod("searchlist", ++ noClasses); ++ lsearch_tmp = (List) searchlist.invoke(resConf, noObjects); ++ } ++ catch (Exception e) { ++ return false; ++ } ++ ++ if (lserver_tmp.size() == 0) ++ return false; ++ ++ if (lserver_tmp.size() > 0) { ++ Iterator it = lserver_tmp.iterator(); ++ while (it.hasNext()) ++ addServer((String) it.next(), lserver); ++ } ++ ++ if (lsearch_tmp.size() > 0) { ++ Iterator it = lsearch_tmp.iterator(); ++ while (it.hasNext()) ++ addSearch((String) it.next(), lsearch); ++ } ++ configureFromLists(lserver, lsearch); ++ return true; ++} ++ ++/** ++ * Looks in /etc/resolv.conf to find servers and a search path. ++ * "nameserver" lines specify servers. "domain" and "search" lines ++ * define the search path. ++ */ ++private void ++findResolvConf(String file) { ++ InputStream in = null; ++ try { ++ in = new FileInputStream(file); ++ } ++ catch (FileNotFoundException e) { ++ return; ++ } ++ InputStreamReader isr = new InputStreamReader(in); ++ BufferedReader br = new BufferedReader(isr); ++ List lserver = new ArrayList(0); ++ List lsearch = new ArrayList(0); ++ int lndots = -1; ++ try { ++ String line; ++ while ((line = br.readLine()) != null) { ++ if (line.startsWith("nameserver")) { ++ StringTokenizer st = new StringTokenizer(line); ++ st.nextToken(); /* skip nameserver */ ++ addServer(st.nextToken(), lserver); ++ } ++ else if (line.startsWith("domain")) { ++ StringTokenizer st = new StringTokenizer(line); ++ st.nextToken(); /* skip domain */ ++ if (!st.hasMoreTokens()) ++ continue; ++ if (lsearch.isEmpty()) ++ addSearch(st.nextToken(), lsearch); ++ } ++ else if (line.startsWith("search")) { ++ if (!lsearch.isEmpty()) ++ lsearch.clear(); ++ StringTokenizer st = new StringTokenizer(line); ++ st.nextToken(); /* skip search */ ++ while (st.hasMoreTokens()) ++ addSearch(st.nextToken(), lsearch); ++ } ++ else if(line.startsWith("options")) { ++ StringTokenizer st = new StringTokenizer(line); ++ st.nextToken(); /* skip options */ ++ while (st.hasMoreTokens()) { ++ String token = st.nextToken(); ++ if (token.startsWith("ndots:")) { ++ lndots = parseNdots(token); ++ } ++ } ++ } ++ } ++ br.close(); ++ } ++ catch (IOException e) { ++ } ++ ++ configureFromLists(lserver, lsearch); ++ configureNdots(lndots); ++} ++ ++private void ++findUnix() { ++ findResolvConf("/etc/resolv.conf"); ++} ++ ++private void ++findNetware() { ++ findResolvConf("sys:/etc/resolv.cfg"); ++} ++ ++/** ++ * Parses the output of winipcfg or ipconfig. ++ */ ++private void ++findWin(InputStream in, Locale locale) { ++ String packageName = ResolverConfig.class.getPackage().getName(); ++ String resPackageName = packageName + ".windows.DNSServer"; ++ ResourceBundle res; ++ if (locale != null) ++ res = ResourceBundle.getBundle(resPackageName, locale); ++ else ++ res = ResourceBundle.getBundle(resPackageName); ++ ++ String host_name = res.getString("host_name"); ++ String primary_dns_suffix = res.getString("primary_dns_suffix"); ++ String dns_suffix = res.getString("dns_suffix"); ++ String dns_servers = res.getString("dns_servers"); ++ ++ BufferedReader br = new BufferedReader(new InputStreamReader(in)); ++ try { ++ List lserver = new ArrayList(); ++ List lsearch = new ArrayList(); ++ String line = null; ++ boolean readingServers = false; ++ boolean readingSearches = false; ++ while ((line = br.readLine()) != null) { ++ StringTokenizer st = new StringTokenizer(line); ++ if (!st.hasMoreTokens()) { ++ readingServers = false; ++ readingSearches = false; ++ continue; ++ } ++ String s = st.nextToken(); ++ if (line.indexOf(":") != -1) { ++ readingServers = false; ++ readingSearches = false; ++ } ++ ++ if (line.indexOf(host_name) != -1) { ++ while (st.hasMoreTokens()) ++ s = st.nextToken(); ++ Name name; ++ try { ++ name = Name.fromString(s, null); ++ } ++ catch (TextParseException e) { ++ continue; ++ } ++ if (name.labels() == 1) ++ continue; ++ addSearch(s, lsearch); ++ } else if (line.indexOf(primary_dns_suffix) != -1) { ++ while (st.hasMoreTokens()) ++ s = st.nextToken(); ++ if (s.equals(":")) ++ continue; ++ addSearch(s, lsearch); ++ readingSearches = true; ++ } else if (readingSearches || ++ line.indexOf(dns_suffix) != -1) ++ { ++ while (st.hasMoreTokens()) ++ s = st.nextToken(); ++ if (s.equals(":")) ++ continue; ++ addSearch(s, lsearch); ++ readingSearches = true; ++ } else if (readingServers || ++ line.indexOf(dns_servers) != -1) ++ { ++ while (st.hasMoreTokens()) ++ s = st.nextToken(); ++ if (s.equals(":")) ++ continue; ++ addServer(s, lserver); ++ readingServers = true; ++ } ++ } ++ ++ configureFromLists(lserver, lsearch); ++ } ++ catch (IOException e) { ++ } ++ return; ++} ++ ++private void ++findWin(InputStream in) { ++ String property = "org.xbill.DNS.windows.parse.buffer"; ++ final int defaultBufSize = 8 * 1024; ++ int bufSize = Integer.getInteger(property, defaultBufSize).intValue(); ++ BufferedInputStream b = new BufferedInputStream(in, bufSize); ++ b.mark(bufSize); ++ findWin(b, null); ++ if (servers == null) { ++ try { ++ b.reset(); ++ } ++ catch (IOException e) { ++ return; ++ } ++ findWin(b, new Locale("", "")); ++ } ++} ++ ++/** ++ * Calls winipcfg and parses the result to find servers and a search path. ++ */ ++private void ++find95() { ++ String s = "winipcfg.out"; ++ try { ++ Process p; ++ p = Runtime.getRuntime().exec("winipcfg /all /batch " + s); ++ p.waitFor(); ++ File f = new File(s); ++ findWin(new FileInputStream(f)); ++ new File(s).delete(); ++ } ++ catch (Exception e) { ++ return; ++ } ++} ++ ++/** ++ * Calls ipconfig and parses the result to find servers and a search path. ++ */ ++private void ++findNT() { ++ try { ++ Process p; ++ p = Runtime.getRuntime().exec("ipconfig /all"); ++ findWin(p.getInputStream()); ++ p.destroy(); ++ } ++ catch (Exception e) { ++ return; ++ } ++} ++ ++/** ++ * Parses the output of getprop, which is the only way to get DNS ++ * info on Android. getprop might disappear in future releases, so ++ * this code comes with a use-by date. ++ */ ++private void ++findAndroid() { ++ // This originally looked for all lines containing .dns; but ++ // http://code.google.com/p/android/issues/detail?id=2207#c73 ++ // indicates that net.dns* should always be the active nameservers, so ++ // we use those. ++ String re1 = "^\\d+(\\.\\d+){3}$"; ++ String re2 = "^[0-9a-f]+(:[0-9a-f]*)+:[0-9a-f]+$"; ++ try { ++ ArrayList lserver = new ArrayList(); ++ ArrayList lsearch = new ArrayList(); ++ String line; ++ Process p = Runtime.getRuntime().exec("getprop"); ++ InputStream in = p.getInputStream(); ++ InputStreamReader isr = new InputStreamReader(in); ++ BufferedReader br = new BufferedReader(isr); ++ while ((line = br.readLine()) != null ) { ++ StringTokenizer t = new StringTokenizer(line, ":"); ++ String name = t.nextToken(); ++ if (name.indexOf( "net.dns" ) > -1) { ++ String v = t.nextToken(); ++ v = v.replaceAll("[ \\[\\]]", ""); ++ if ((v.matches(re1) || v.matches(re2)) && ++ !lserver.contains(v)) ++ lserver.add(v); ++ } ++ } ++ configureFromLists(lserver, lsearch); ++ } catch ( Exception e ) { ++ // ignore resolutely ++ } ++} ++ ++/** Returns all located servers */ ++public String [] ++servers() { ++ return servers; ++} ++ ++/** Returns the first located server */ ++public String ++server() { ++ if (servers == null) ++ return null; ++ return servers[0]; ++} ++ ++/** Returns all entries in the located search path */ ++public Name [] ++searchPath() { ++ return searchlist; ++} ++ ++/** ++ * Returns the located ndots value, or the default (1) if not configured. ++ * Note that ndots can only be configured in a resolv.conf file, and will only ++ * take effect if ResolverConfig uses resolv.conf directly (that is, if the ++ * JVM does not include the sun.net.dns.ResolverConfiguration class). ++ */ ++public int ++ndots() { ++ if (ndots < 0) ++ return 1; ++ return ndots; ++} ++ ++/** Gets the current configuration */ ++public static synchronized ResolverConfig ++getCurrentConfig() { ++ return currentConfig; ++} ++ ++/** Gets the current configuration */ ++public static void ++refresh() { ++ ResolverConfig newConfig = new ResolverConfig(); ++ synchronized (ResolverConfig.class) { ++ currentConfig = newConfig; ++ } ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ResolverListener.java b/external/asmack/build/src/trunk/org/xbill/DNS/ResolverListener.java +new file mode 100644 +index 0000000..accf82c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/ResolverListener.java +@@ -0,0 +1,30 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.util.EventListener; ++ ++/** ++ * An interface to the asynchronous resolver. ++ * @see Resolver ++ * ++ * @author Brian Wellington ++ */ ++ ++public interface ResolverListener extends EventListener { ++ ++/** ++ * The callback used by an asynchronous resolver ++ * @param id The identifier returned by Resolver.sendAsync() ++ * @param m The response message as returned by the Resolver ++ */ ++void receiveMessage(Object id, Message m); ++ ++/** ++ * The callback used by an asynchronous resolver when an exception is thrown ++ * @param id The identifier returned by Resolver.sendAsync() ++ * @param e The thrown exception ++ */ ++void handleException(Object id, Exception e); ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ReverseMap.java b/external/asmack/build/src/trunk/org/xbill/DNS/ReverseMap.java +new file mode 100644 +index 0000000..f4293de +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/ReverseMap.java +@@ -0,0 +1,130 @@ ++// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.net.*; ++ ++/** ++ * A set functions designed to deal with DNS names used in reverse mappings. ++ * For the IPv4 address a.b.c.d, the reverse map name is d.c.b.a.in-addr.arpa. ++ * For an IPv6 address, the reverse map name is ...ip6.arpa. ++ * ++ * @author Brian Wellington ++ */ ++ ++public final class ReverseMap { ++ ++private static Name inaddr4 = Name.fromConstantString("in-addr.arpa."); ++private static Name inaddr6 = Name.fromConstantString("ip6.arpa."); ++ ++/* Otherwise the class could be instantiated */ ++private ++ReverseMap() {} ++ ++/** ++ * Creates a reverse map name corresponding to an address contained in ++ * an array of 4 bytes (for an IPv4 address) or 16 bytes (for an IPv6 address). ++ * @param addr The address from which to build a name. ++ * @return The name corresponding to the address in the reverse map. ++ */ ++public static Name ++fromAddress(byte [] addr) { ++ if (addr.length != 4 && addr.length != 16) ++ throw new IllegalArgumentException("array must contain " + ++ "4 or 16 elements"); ++ ++ StringBuffer sb = new StringBuffer(); ++ if (addr.length == 4) { ++ for (int i = addr.length - 1; i >= 0; i--) { ++ sb.append(addr[i] & 0xFF); ++ if (i > 0) ++ sb.append("."); ++ } ++ } else { ++ int [] nibbles = new int[2]; ++ for (int i = addr.length - 1; i >= 0; i--) { ++ nibbles[0] = (addr[i] & 0xFF) >> 4; ++ nibbles[1] = (addr[i] & 0xFF) & 0xF; ++ for (int j = nibbles.length - 1; j >= 0; j--) { ++ sb.append(Integer.toHexString(nibbles[j])); ++ if (i > 0 || j > 0) ++ sb.append("."); ++ } ++ } ++ } ++ ++ try { ++ if (addr.length == 4) ++ return Name.fromString(sb.toString(), inaddr4); ++ else ++ return Name.fromString(sb.toString(), inaddr6); ++ } ++ catch (TextParseException e) { ++ throw new IllegalStateException("name cannot be invalid"); ++ } ++} ++ ++/** ++ * Creates a reverse map name corresponding to an address contained in ++ * an array of 4 integers between 0 and 255 (for an IPv4 address) or 16 ++ * integers between 0 and 255 (for an IPv6 address). ++ * @param addr The address from which to build a name. ++ * @return The name corresponding to the address in the reverse map. ++ */ ++public static Name ++fromAddress(int [] addr) { ++ byte [] bytes = new byte[addr.length]; ++ for (int i = 0; i < addr.length; i++) { ++ if (addr[i] < 0 || addr[i] > 0xFF) ++ throw new IllegalArgumentException("array must " + ++ "contain values " + ++ "between 0 and 255"); ++ bytes[i] = (byte) addr[i]; ++ } ++ return fromAddress(bytes); ++} ++ ++/** ++ * Creates a reverse map name corresponding to an address contained in ++ * an InetAddress. ++ * @param addr The address from which to build a name. ++ * @return The name corresponding to the address in the reverse map. ++ */ ++public static Name ++fromAddress(InetAddress addr) { ++ return fromAddress(addr.getAddress()); ++} ++ ++/** ++ * Creates a reverse map name corresponding to an address contained in ++ * a String. ++ * @param addr The address from which to build a name. ++ * @return The name corresponding to the address in the reverse map. ++ * @throws UnknownHostException The string does not contain a valid address. ++ */ ++public static Name ++fromAddress(String addr, int family) throws UnknownHostException { ++ byte [] array = Address.toByteArray(addr, family); ++ if (array == null) ++ throw new UnknownHostException("Invalid IP address"); ++ return fromAddress(array); ++} ++ ++/** ++ * Creates a reverse map name corresponding to an address contained in ++ * a String. ++ * @param addr The address from which to build a name. ++ * @return The name corresponding to the address in the reverse map. ++ * @throws UnknownHostException The string does not contain a valid address. ++ */ ++public static Name ++fromAddress(String addr) throws UnknownHostException { ++ byte [] array = Address.toByteArray(addr, Address.IPv4); ++ if (array == null) ++ array = Address.toByteArray(addr, Address.IPv6); ++ if (array == null) ++ throw new UnknownHostException("Invalid IP address"); ++ return fromAddress(array); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SIG0.java b/external/asmack/build/src/trunk/org/xbill/DNS/SIG0.java +new file mode 100644 +index 0000000..5a00e72 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/SIG0.java +@@ -0,0 +1,79 @@ ++// Copyright (c) 2001-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.security.PrivateKey; ++import java.util.Date; ++ ++/** ++ * Creates SIG(0) transaction signatures. ++ * ++ * @author Pasi Eronen ++ * @author Brian Wellington ++ */ ++ ++public class SIG0 { ++ ++/** ++ * The default validity period for outgoing SIG(0) signed messages. ++ * Can be overriden by the sig0validity option. ++ */ ++private static final short VALIDITY = 300; ++ ++private ++SIG0() { } ++ ++/** ++ * Sign a message with SIG(0). The DNS key and private key must refer to the ++ * same underlying cryptographic key. ++ * @param message The message to be signed ++ * @param key The DNSKEY record to use as part of signing ++ * @param privkey The PrivateKey to use when signing ++ * @param previous If this message is a response, the SIG(0) from the query ++ */ ++public static void ++signMessage(Message message, KEYRecord key, PrivateKey privkey, ++ SIGRecord previous) throws DNSSEC.DNSSECException ++{ ++ ++ int validity = Options.intValue("sig0validity"); ++ if (validity < 0) ++ validity = VALIDITY; ++ ++ long now = System.currentTimeMillis(); ++ Date timeSigned = new Date(now); ++ Date timeExpires = new Date(now + validity * 1000); ++ ++ SIGRecord sig = DNSSEC.signMessage(message, previous, key, privkey, ++ timeSigned, timeExpires); ++ ++ message.addRecord(sig, Section.ADDITIONAL); ++} ++ ++/** ++ * Verify a message using SIG(0). ++ * @param message The message to be signed ++ * @param b An array containing the message in unparsed form. This is ++ * necessary since SIG(0) signs the message in wire format, and we can't ++ * recreate the exact wire format (with the same name compression). ++ * @param key The KEY record to verify the signature with. ++ * @param previous If this message is a response, the SIG(0) from the query ++ */ ++public static void ++verifyMessage(Message message, byte [] b, KEYRecord key, SIGRecord previous) ++ throws DNSSEC.DNSSECException ++{ ++ SIGRecord sig = null; ++ Record [] additional = message.getSectionArray(Section.ADDITIONAL); ++ for (int i = 0; i < additional.length; i++) { ++ if (additional[i].getType() != Type.SIG) ++ continue; ++ if (((SIGRecord) additional[i]).getTypeCovered() != 0) ++ continue; ++ sig = (SIGRecord) additional[i]; ++ break; ++ } ++ DNSSEC.verifyMessage(message, b, sig, previous, key); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SIGBase.java b/external/asmack/build/src/trunk/org/xbill/DNS/SIGBase.java +new file mode 100644 +index 0000000..6e5f12d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/SIGBase.java +@@ -0,0 +1,193 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.util.*; ++import org.xbill.DNS.utils.*; ++ ++/** ++ * The base class for SIG/RRSIG records, which have identical formats ++ * ++ * @author Brian Wellington ++ */ ++ ++abstract class SIGBase extends Record { ++ ++private static final long serialVersionUID = -3738444391533812369L; ++ ++protected int covered; ++protected int alg, labels; ++protected long origttl; ++protected Date expire, timeSigned; ++protected int footprint; ++protected Name signer; ++protected byte [] signature; ++ ++protected ++SIGBase() {} ++ ++public ++SIGBase(Name name, int type, int dclass, long ttl, int covered, int alg, ++ long origttl, Date expire, Date timeSigned, int footprint, Name signer, ++ byte [] signature) ++{ ++ super(name, type, dclass, ttl); ++ Type.check(covered); ++ TTL.check(origttl); ++ this.covered = covered; ++ this.alg = checkU8("alg", alg); ++ this.labels = name.labels() - 1; ++ if (name.isWild()) ++ this.labels--; ++ this.origttl = origttl; ++ this.expire = expire; ++ this.timeSigned = timeSigned; ++ this.footprint = checkU16("footprint", footprint); ++ this.signer = checkName("signer", signer); ++ this.signature = signature; ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ covered = in.readU16(); ++ alg = in.readU8(); ++ labels = in.readU8(); ++ origttl = in.readU32(); ++ expire = new Date(1000 * in.readU32()); ++ timeSigned = new Date(1000 * in.readU32()); ++ footprint = in.readU16(); ++ signer = new Name(in); ++ signature = in.readByteArray(); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ String typeString = st.getString(); ++ covered = Type.value(typeString); ++ if (covered < 0) ++ throw st.exception("Invalid type: " + typeString); ++ String algString = st.getString(); ++ alg = DNSSEC.Algorithm.value(algString); ++ if (alg < 0) ++ throw st.exception("Invalid algorithm: " + algString); ++ labels = st.getUInt8(); ++ origttl = st.getTTL(); ++ expire = FormattedTime.parse(st.getString()); ++ timeSigned = FormattedTime.parse(st.getString()); ++ footprint = st.getUInt16(); ++ signer = st.getName(origin); ++ signature = st.getBase64(); ++} ++ ++/** Converts the RRSIG/SIG Record to a String */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append (Type.string(covered)); ++ sb.append (" "); ++ sb.append (alg); ++ sb.append (" "); ++ sb.append (labels); ++ sb.append (" "); ++ sb.append (origttl); ++ sb.append (" "); ++ if (Options.check("multiline")) ++ sb.append ("(\n\t"); ++ sb.append (FormattedTime.format(expire)); ++ sb.append (" "); ++ sb.append (FormattedTime.format(timeSigned)); ++ sb.append (" "); ++ sb.append (footprint); ++ sb.append (" "); ++ sb.append (signer); ++ if (Options.check("multiline")) { ++ sb.append("\n"); ++ sb.append(base64.formatString(signature, 64, "\t", ++ true)); ++ } else { ++ sb.append (" "); ++ sb.append(base64.toString(signature)); ++ } ++ return sb.toString(); ++} ++ ++/** Returns the RRset type covered by this signature */ ++public int ++getTypeCovered() { ++ return covered; ++} ++ ++/** ++ * Returns the cryptographic algorithm of the key that generated the signature ++ */ ++public int ++getAlgorithm() { ++ return alg; ++} ++ ++/** ++ * Returns the number of labels in the signed domain name. This may be ++ * different than the record's domain name if the record is a wildcard ++ * record. ++ */ ++public int ++getLabels() { ++ return labels; ++} ++ ++/** Returns the original TTL of the RRset */ ++public long ++getOrigTTL() { ++ return origttl; ++} ++ ++/** Returns the time at which the signature expires */ ++public Date ++getExpire() { ++ return expire; ++} ++ ++/** Returns the time at which this signature was generated */ ++public Date ++getTimeSigned() { ++ return timeSigned; ++} ++ ++/** Returns The footprint/key id of the signing key. */ ++public int ++getFootprint() { ++ return footprint; ++} ++ ++/** Returns the owner of the signing key */ ++public Name ++getSigner() { ++ return signer; ++} ++ ++/** Returns the binary data representing the signature */ ++public byte [] ++getSignature() { ++ return signature; ++} ++ ++void ++setSignature(byte [] signature) { ++ this.signature = signature; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeU16(covered); ++ out.writeU8(alg); ++ out.writeU8(labels); ++ out.writeU32(origttl); ++ out.writeU32(expire.getTime() / 1000); ++ out.writeU32(timeSigned.getTime() / 1000); ++ out.writeU16(footprint); ++ signer.toWire(out, null, canonical); ++ out.writeByteArray(signature); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SIGRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/SIGRecord.java +new file mode 100644 +index 0000000..8b6f58d +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/SIGRecord.java +@@ -0,0 +1,50 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.util.*; ++ ++/** ++ * Signature - A SIG provides the digital signature of an RRset, so that ++ * the data can be authenticated by a DNSSEC-capable resolver. The ++ * signature is usually generated by a key contained in a KEYRecord ++ * @see RRset ++ * @see DNSSEC ++ * @see KEYRecord ++ * ++ * @author Brian Wellington ++ */ ++ ++public class SIGRecord extends SIGBase { ++ ++private static final long serialVersionUID = 4963556060953589058L; ++ ++SIGRecord() {} ++ ++Record ++getObject() { ++ return new SIGRecord(); ++} ++ ++/** ++ * Creates an SIG Record from the given data ++ * @param covered The RRset type covered by this signature ++ * @param alg The cryptographic algorithm of the key that generated the ++ * signature ++ * @param origttl The original TTL of the RRset ++ * @param expire The time at which the signature expires ++ * @param timeSigned The time at which this signature was generated ++ * @param footprint The footprint/key id of the signing key. ++ * @param signer The owner of the signing key ++ * @param signature Binary data representing the signature ++ */ ++public ++SIGRecord(Name name, int dclass, long ttl, int covered, int alg, long origttl, ++ Date expire, Date timeSigned, int footprint, Name signer, ++ byte [] signature) ++{ ++ super(name, Type.SIG, dclass, ttl, covered, alg, origttl, expire, ++ timeSigned, footprint, signer, signature); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SOARecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/SOARecord.java +new file mode 100644 +index 0000000..7f27077 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/SOARecord.java +@@ -0,0 +1,162 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++/** ++ * Start of Authority - describes properties of a zone. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class SOARecord extends Record { ++ ++private static final long serialVersionUID = 1049740098229303931L; ++ ++private Name host, admin; ++private long serial, refresh, retry, expire, minimum; ++ ++SOARecord() {} ++ ++Record ++getObject() { ++ return new SOARecord(); ++} ++ ++/** ++ * Creates an SOA Record from the given data ++ * @param host The primary name server for the zone ++ * @param admin The zone administrator's address ++ * @param serial The zone's serial number ++ * @param refresh The amount of time until a secondary checks for a new serial ++ * number ++ * @param retry The amount of time between a secondary's checks for a new ++ * serial number ++ * @param expire The amount of time until a secondary expires a zone ++ * @param minimum The minimum TTL for records in the zone ++*/ ++public ++SOARecord(Name name, int dclass, long ttl, Name host, Name admin, ++ long serial, long refresh, long retry, long expire, long minimum) ++{ ++ super(name, Type.SOA, dclass, ttl); ++ this.host = checkName("host", host); ++ this.admin = checkName("admin", admin); ++ this.serial = checkU32("serial", serial); ++ this.refresh = checkU32("refresh", refresh); ++ this.retry = checkU32("retry", retry); ++ this.expire = checkU32("expire", expire); ++ this.minimum = checkU32("minimum", minimum); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ host = new Name(in); ++ admin = new Name(in); ++ serial = in.readU32(); ++ refresh = in.readU32(); ++ retry = in.readU32(); ++ expire = in.readU32(); ++ minimum = in.readU32(); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ host = st.getName(origin); ++ admin = st.getName(origin); ++ serial = st.getUInt32(); ++ refresh = st.getTTLLike(); ++ retry = st.getTTLLike(); ++ expire = st.getTTLLike(); ++ minimum = st.getTTLLike(); ++} ++ ++/** Convert to a String */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(host); ++ sb.append(" "); ++ sb.append(admin); ++ if (Options.check("multiline")) { ++ sb.append(" (\n\t\t\t\t\t"); ++ sb.append(serial); ++ sb.append("\t; serial\n\t\t\t\t\t"); ++ sb.append(refresh); ++ sb.append("\t; refresh\n\t\t\t\t\t"); ++ sb.append(retry); ++ sb.append("\t; retry\n\t\t\t\t\t"); ++ sb.append(expire); ++ sb.append("\t; expire\n\t\t\t\t\t"); ++ sb.append(minimum); ++ sb.append(" )\t; minimum"); ++ } else { ++ sb.append(" "); ++ sb.append(serial); ++ sb.append(" "); ++ sb.append(refresh); ++ sb.append(" "); ++ sb.append(retry); ++ sb.append(" "); ++ sb.append(expire); ++ sb.append(" "); ++ sb.append(minimum); ++ } ++ return sb.toString(); ++} ++ ++/** Returns the primary name server */ ++public Name ++getHost() { ++ return host; ++} ++ ++/** Returns the zone administrator's address */ ++public Name ++getAdmin() { ++ return admin; ++} ++ ++/** Returns the zone's serial number */ ++public long ++getSerial() { ++ return serial; ++} ++ ++/** Returns the zone refresh interval */ ++public long ++getRefresh() { ++ return refresh; ++} ++ ++/** Returns the zone retry interval */ ++public long ++getRetry() { ++ return retry; ++} ++ ++/** Returns the time until a secondary expires a zone */ ++public long ++getExpire() { ++ return expire; ++} ++ ++/** Returns the minimum TTL for records in the zone */ ++public long ++getMinimum() { ++ return minimum; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ host.toWire(out, c, canonical); ++ admin.toWire(out, c, canonical); ++ out.writeU32(serial); ++ out.writeU32(refresh); ++ out.writeU32(retry); ++ out.writeU32(expire); ++ out.writeU32(minimum); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SPFRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/SPFRecord.java +new file mode 100644 +index 0000000..a286220 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/SPFRecord.java +@@ -0,0 +1,44 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.util.*; ++ ++/** ++ * Sender Policy Framework (RFC 4408, experimental) ++ * ++ * @author Brian Wellington ++ */ ++ ++public class SPFRecord extends TXTBase { ++ ++private static final long serialVersionUID = -2100754352801658722L; ++ ++SPFRecord() {} ++ ++Record ++getObject() { ++ return new SPFRecord(); ++} ++ ++/** ++ * Creates a SPF Record from the given data ++ * @param strings The text strings ++ * @throws IllegalArgumentException One of the strings has invalid escapes ++ */ ++public ++SPFRecord(Name name, int dclass, long ttl, List strings) { ++ super(name, Type.SPF, dclass, ttl, strings); ++} ++ ++/** ++ * Creates a SPF Record from the given data ++ * @param string One text string ++ * @throws IllegalArgumentException The string has invalid escapes ++ */ ++public ++SPFRecord(Name name, int dclass, long ttl, String string) { ++ super(name, Type.SPF, dclass, ttl, string); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SRVRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/SRVRecord.java +new file mode 100644 +index 0000000..c0635fb +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/SRVRecord.java +@@ -0,0 +1,114 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++/** ++ * Server Selection Record - finds hosts running services in a domain. An ++ * SRV record will normally be named _<service>._<protocol>.domain ++ * - examples would be _sips._tcp.example.org (for the secure SIP protocol) and ++ * _http._tcp.example.com (if HTTP used SRV records) ++ * ++ * @author Brian Wellington ++ */ ++ ++public class SRVRecord extends Record { ++ ++private static final long serialVersionUID = -3886460132387522052L; ++ ++private int priority, weight, port; ++private Name target; ++ ++SRVRecord() {} ++ ++Record ++getObject() { ++ return new SRVRecord(); ++} ++ ++/** ++ * Creates an SRV Record from the given data ++ * @param priority The priority of this SRV. Records with lower priority ++ * are preferred. ++ * @param weight The weight, used to select between records at the same ++ * priority. ++ * @param port The TCP/UDP port that the service uses ++ * @param target The host running the service ++ */ ++public ++SRVRecord(Name name, int dclass, long ttl, int priority, ++ int weight, int port, Name target) ++{ ++ super(name, Type.SRV, dclass, ttl); ++ this.priority = checkU16("priority", priority); ++ this.weight = checkU16("weight", weight); ++ this.port = checkU16("port", port); ++ this.target = checkName("target", target); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ priority = in.readU16(); ++ weight = in.readU16(); ++ port = in.readU16(); ++ target = new Name(in); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ priority = st.getUInt16(); ++ weight = st.getUInt16(); ++ port = st.getUInt16(); ++ target = st.getName(origin); ++} ++ ++/** Converts rdata to a String */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(priority + " "); ++ sb.append(weight + " "); ++ sb.append(port + " "); ++ sb.append(target); ++ return sb.toString(); ++} ++ ++/** Returns the priority */ ++public int ++getPriority() { ++ return priority; ++} ++ ++/** Returns the weight */ ++public int ++getWeight() { ++ return weight; ++} ++ ++/** Returns the port that the service runs on */ ++public int ++getPort() { ++ return port; ++} ++ ++/** Returns the host running that the service */ ++public Name ++getTarget() { ++ return target; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeU16(priority); ++ out.writeU16(weight); ++ out.writeU16(port); ++ target.toWire(out, null, canonical); ++} ++ ++public Name ++getAdditionalName() { ++ return target; ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SSHFPRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/SSHFPRecord.java +new file mode 100644 +index 0000000..079741e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/SSHFPRecord.java +@@ -0,0 +1,108 @@ ++// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import org.xbill.DNS.utils.*; ++ ++/** ++ * SSH Fingerprint - stores the fingerprint of an SSH host key. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class SSHFPRecord extends Record { ++ ++private static final long serialVersionUID = -8104701402654687025L; ++ ++public static class Algorithm { ++ private Algorithm() {} ++ ++ public static final int RSA = 1; ++ public static final int DSS = 2; ++} ++ ++public static class Digest { ++ private Digest() {} ++ ++ public static final int SHA1 = 1; ++} ++ ++private int alg; ++private int digestType; ++private byte [] fingerprint; ++ ++SSHFPRecord() {} ++ ++Record ++getObject() { ++ return new SSHFPRecord(); ++} ++ ++/** ++ * Creates an SSHFP Record from the given data. ++ * @param alg The public key's algorithm. ++ * @param digestType The public key's digest type. ++ * @param fingerprint The public key's fingerprint. ++ */ ++public ++SSHFPRecord(Name name, int dclass, long ttl, int alg, int digestType, ++ byte [] fingerprint) ++{ ++ super(name, Type.SSHFP, dclass, ttl); ++ this.alg = checkU8("alg", alg); ++ this.digestType = checkU8("digestType", digestType); ++ this.fingerprint = fingerprint; ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ alg = in.readU8(); ++ digestType = in.readU8(); ++ fingerprint = in.readByteArray(); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ alg = st.getUInt8(); ++ digestType = st.getUInt8(); ++ fingerprint = st.getHex(true); ++} ++ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(alg); ++ sb.append(" "); ++ sb.append(digestType); ++ sb.append(" "); ++ sb.append(base16.toString(fingerprint)); ++ return sb.toString(); ++} ++ ++/** Returns the public key's algorithm. */ ++public int ++getAlgorithm() { ++ return alg; ++} ++ ++/** Returns the public key's digest type. */ ++public int ++getDigestType() { ++ return digestType; ++} ++ ++/** Returns the fingerprint */ ++public byte [] ++getFingerPrint() { ++ return fingerprint; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeU8(alg); ++ out.writeU8(digestType); ++ out.writeByteArray(fingerprint); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Section.java b/external/asmack/build/src/trunk/org/xbill/DNS/Section.java +new file mode 100644 +index 0000000..e0c8caa +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Section.java +@@ -0,0 +1,92 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Constants and functions relating to DNS message sections ++ * ++ * @author Brian Wellington ++ */ ++ ++public final class Section { ++ ++/** The question (first) section */ ++public static final int QUESTION = 0; ++ ++/** The answer (second) section */ ++public static final int ANSWER = 1; ++ ++/** The authority (third) section */ ++public static final int AUTHORITY = 2; ++ ++/** The additional (fourth) section */ ++public static final int ADDITIONAL = 3; ++ ++/* Aliases for dynamic update */ ++/** The zone (first) section of a dynamic update message */ ++public static final int ZONE = 0; ++ ++/** The prerequisite (second) section of a dynamic update message */ ++public static final int PREREQ = 1; ++ ++/** The update (third) section of a dynamic update message */ ++public static final int UPDATE = 2; ++ ++private static Mnemonic sections = new Mnemonic("Message Section", ++ Mnemonic.CASE_LOWER); ++private static String [] longSections = new String[4]; ++private static String [] updateSections = new String[4]; ++ ++static { ++ sections.setMaximum(3); ++ sections.setNumericAllowed(true); ++ ++ sections.add(QUESTION, "qd"); ++ sections.add(ANSWER, "an"); ++ sections.add(AUTHORITY, "au"); ++ sections.add(ADDITIONAL, "ad"); ++ ++ longSections[QUESTION] = "QUESTIONS"; ++ longSections[ANSWER] = "ANSWERS"; ++ longSections[AUTHORITY] = "AUTHORITY RECORDS"; ++ longSections[ADDITIONAL] = "ADDITIONAL RECORDS"; ++ ++ updateSections[ZONE] = "ZONE"; ++ updateSections[PREREQ] = "PREREQUISITES"; ++ updateSections[UPDATE] = "UPDATE RECORDS"; ++ updateSections[ADDITIONAL] = "ADDITIONAL RECORDS"; ++} ++ ++private ++Section() {} ++ ++/** Converts a numeric Section into an abbreviation String */ ++public static String ++string(int i) { ++ return sections.getText(i); ++} ++ ++/** Converts a numeric Section into a full description String */ ++public static String ++longString(int i) { ++ sections.check(i); ++ return longSections[i]; ++} ++ ++/** ++ * Converts a numeric Section into a full description String for an update ++ * Message. ++ */ ++public static String ++updString(int i) { ++ sections.check(i); ++ return updateSections[i]; ++} ++ ++/** Converts a String representation of a Section into its numeric value */ ++public static int ++value(String s) { ++ return sections.getValue(s); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Serial.java b/external/asmack/build/src/trunk/org/xbill/DNS/Serial.java +new file mode 100644 +index 0000000..3a146c6 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Serial.java +@@ -0,0 +1,61 @@ ++// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Helper functions for doing serial arithmetic. These should be used when ++ * setting/checking SOA serial numbers. SOA serial number arithmetic is ++ * defined in RFC 1982. ++ * ++ * @author Brian Wellington ++ */ ++ ++public final class Serial { ++ ++private static final long MAX32 = 0xFFFFFFFFL; ++ ++private ++Serial() { ++} ++ ++/** ++ * Compares two numbers using serial arithmetic. The numbers are assumed ++ * to be 32 bit unsigned integers stored in longs. ++ * @param serial1 The first integer ++ * @param serial2 The second integer ++ * @return 0 if the 2 numbers are equal, a positive number if serial1 is greater ++ * than serial2, and a negative number if serial2 is greater than serial1. ++ * @throws IllegalArgumentException serial1 or serial2 is out of range ++ */ ++public static int ++compare(long serial1, long serial2) { ++ if (serial1 < 0 || serial1 > MAX32) ++ throw new IllegalArgumentException(serial1 + " out of range"); ++ if (serial2 < 0 || serial2 > MAX32) ++ throw new IllegalArgumentException(serial2 + " out of range"); ++ long diff = serial1 - serial2; ++ if (diff >= MAX32) ++ diff -= (MAX32 + 1); ++ else if (diff < -MAX32) ++ diff += (MAX32 + 1); ++ return (int)diff; ++} ++ ++/** ++ * Increments a serial number. The number is assumed to be a 32 bit unsigned ++ * integer stored in a long. This basically adds 1 and resets the value to ++ * 0 if it is 2^32. ++ * @param serial The serial number ++ * @return The incremented serial number ++ * @throws IllegalArgumentException serial is out of range ++ */ ++public static long ++increment(long serial) { ++ if (serial < 0 || serial > MAX32) ++ throw new IllegalArgumentException(serial + " out of range"); ++ if (serial == MAX32) ++ return 0; ++ return serial + 1; ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SetResponse.java b/external/asmack/build/src/trunk/org/xbill/DNS/SetResponse.java +new file mode 100644 +index 0000000..05d9f32 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/SetResponse.java +@@ -0,0 +1,202 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.util.*; ++ ++/** ++ * The Response from a query to Cache.lookupRecords() or Zone.findRecords() ++ * @see Cache ++ * @see Zone ++ * ++ * @author Brian Wellington ++ */ ++ ++public class SetResponse { ++ ++/** ++ * The Cache contains no information about the requested name/type ++ */ ++static final int UNKNOWN = 0; ++ ++/** ++ * The Zone does not contain the requested name, or the Cache has ++ * determined that the name does not exist. ++ */ ++static final int NXDOMAIN = 1; ++ ++/** ++ * The Zone contains the name, but no data of the requested type, ++ * or the Cache has determined that the name exists and has no data ++ * of the requested type. ++ */ ++static final int NXRRSET = 2; ++ ++/** ++ * A delegation enclosing the requested name was found. ++ */ ++static final int DELEGATION = 3; ++ ++/** ++ * The Cache/Zone found a CNAME when looking for the name. ++ * @see CNAMERecord ++ */ ++static final int CNAME = 4; ++ ++/** ++ * The Cache/Zone found a DNAME when looking for the name. ++ * @see DNAMERecord ++ */ ++static final int DNAME = 5; ++ ++/** ++ * The Cache/Zone has successfully answered the question for the ++ * requested name/type/class. ++ */ ++static final int SUCCESSFUL = 6; ++ ++private static final SetResponse unknown = new SetResponse(UNKNOWN); ++private static final SetResponse nxdomain = new SetResponse(NXDOMAIN); ++private static final SetResponse nxrrset = new SetResponse(NXRRSET); ++ ++private int type; ++private Object data; ++ ++private ++SetResponse() {} ++ ++SetResponse(int type, RRset rrset) { ++ if (type < 0 || type > 6) ++ throw new IllegalArgumentException("invalid type"); ++ this.type = type; ++ this.data = rrset; ++} ++ ++SetResponse(int type) { ++ if (type < 0 || type > 6) ++ throw new IllegalArgumentException("invalid type"); ++ this.type = type; ++ this.data = null; ++} ++ ++static SetResponse ++ofType(int type) { ++ switch (type) { ++ case UNKNOWN: ++ return unknown; ++ case NXDOMAIN: ++ return nxdomain; ++ case NXRRSET: ++ return nxrrset; ++ case DELEGATION: ++ case CNAME: ++ case DNAME: ++ case SUCCESSFUL: ++ SetResponse sr = new SetResponse(); ++ sr.type = type; ++ sr.data = null; ++ return sr; ++ default: ++ throw new IllegalArgumentException("invalid type"); ++ } ++} ++ ++void ++addRRset(RRset rrset) { ++ if (data == null) ++ data = new ArrayList(); ++ List l = (List) data; ++ l.add(rrset); ++} ++ ++/** Is the answer to the query unknown? */ ++public boolean ++isUnknown() { ++ return (type == UNKNOWN); ++} ++ ++/** Is the answer to the query that the name does not exist? */ ++public boolean ++isNXDOMAIN() { ++ return (type == NXDOMAIN); ++} ++ ++/** Is the answer to the query that the name exists, but the type does not? */ ++public boolean ++isNXRRSET() { ++ return (type == NXRRSET); ++} ++ ++/** Is the result of the lookup that the name is below a delegation? */ ++public boolean ++isDelegation() { ++ return (type == DELEGATION); ++} ++ ++/** Is the result of the lookup a CNAME? */ ++public boolean ++isCNAME() { ++ return (type == CNAME); ++} ++ ++/** Is the result of the lookup a DNAME? */ ++public boolean ++isDNAME() { ++ return (type == DNAME); ++} ++ ++/** Was the query successful? */ ++public boolean ++isSuccessful() { ++ return (type == SUCCESSFUL); ++} ++ ++/** If the query was successful, return the answers */ ++public RRset [] ++answers() { ++ if (type != SUCCESSFUL) ++ return null; ++ List l = (List) data; ++ return (RRset []) l.toArray(new RRset[l.size()]); ++} ++ ++/** ++ * If the query encountered a CNAME, return it. ++ */ ++public CNAMERecord ++getCNAME() { ++ return (CNAMERecord)((RRset)data).first(); ++} ++ ++/** ++ * If the query encountered a DNAME, return it. ++ */ ++public DNAMERecord ++getDNAME() { ++ return (DNAMERecord)((RRset)data).first(); ++} ++ ++/** ++ * If the query hit a delegation point, return the NS set. ++ */ ++public RRset ++getNS() { ++ return (RRset)data; ++} ++ ++/** Prints the value of the SetResponse */ ++public String ++toString() { ++ switch (type) { ++ case UNKNOWN: return "unknown"; ++ case NXDOMAIN: return "NXDOMAIN"; ++ case NXRRSET: return "NXRRSET"; ++ case DELEGATION: return "delegation: " + data; ++ case CNAME: return "CNAME: " + data; ++ case DNAME: return "DNAME: " + data; ++ case SUCCESSFUL: return "successful"; ++ default: throw new IllegalStateException(); ++ } ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SimpleResolver.java b/external/asmack/build/src/trunk/org/xbill/DNS/SimpleResolver.java +new file mode 100644 +index 0000000..7436133 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/SimpleResolver.java +@@ -0,0 +1,351 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.util.*; ++import java.io.*; ++import java.net.*; ++ ++/** ++ * An implementation of Resolver that sends one query to one server. ++ * SimpleResolver handles TCP retries, transaction security (TSIG), and ++ * EDNS 0. ++ * @see Resolver ++ * @see TSIG ++ * @see OPTRecord ++ * ++ * @author Brian Wellington ++ */ ++ ++ ++public class SimpleResolver implements Resolver { ++ ++/** The default port to send queries to */ ++public static final int DEFAULT_PORT = 53; ++ ++/** The default EDNS payload size */ ++public static final int DEFAULT_EDNS_PAYLOADSIZE = 1280; ++ ++private InetSocketAddress address; ++private InetSocketAddress localAddress; ++private boolean useTCP, ignoreTruncation; ++private OPTRecord queryOPT; ++private TSIG tsig; ++private long timeoutValue = 10 * 1000; ++ ++private static final short DEFAULT_UDPSIZE = 512; ++ ++private static String defaultResolver = "localhost"; ++private static int uniqueID = 0; ++ ++/** ++ * Creates a SimpleResolver that will query the specified host ++ * @exception UnknownHostException Failure occurred while finding the host ++ */ ++public ++SimpleResolver(String hostname) throws UnknownHostException { ++ if (hostname == null) { ++ hostname = ResolverConfig.getCurrentConfig().server(); ++ if (hostname == null) ++ hostname = defaultResolver; ++ } ++ InetAddress addr; ++ if (hostname.equals("0")) ++ addr = InetAddress.getLocalHost(); ++ else ++ addr = InetAddress.getByName(hostname); ++ address = new InetSocketAddress(addr, DEFAULT_PORT); ++} ++ ++/** ++ * Creates a SimpleResolver. The host to query is either found by using ++ * ResolverConfig, or the default host is used. ++ * @see ResolverConfig ++ * @exception UnknownHostException Failure occurred while finding the host ++ */ ++public ++SimpleResolver() throws UnknownHostException { ++ this(null); ++} ++ ++/** ++ * Gets the destination address associated with this SimpleResolver. ++ * Messages sent using this SimpleResolver will be sent to this address. ++ * @return The destination address associated with this SimpleResolver. ++ */ ++InetSocketAddress ++getAddress() { ++ return address; ++} ++ ++/** Sets the default host (initially localhost) to query */ ++public static void ++setDefaultResolver(String hostname) { ++ defaultResolver = hostname; ++} ++ ++public void ++setPort(int port) { ++ address = new InetSocketAddress(address.getAddress(), port); ++} ++ ++/** ++ * Sets the address of the server to communicate with. ++ * @param addr The address of the DNS server ++ */ ++public void ++setAddress(InetSocketAddress addr) { ++ address = addr; ++} ++ ++/** ++ * Sets the address of the server to communicate with (on the default ++ * DNS port) ++ * @param addr The address of the DNS server ++ */ ++public void ++setAddress(InetAddress addr) { ++ address = new InetSocketAddress(addr, address.getPort()); ++} ++ ++/** ++ * Sets the local address to bind to when sending messages. ++ * @param addr The local address to send messages from. ++ */ ++public void ++setLocalAddress(InetSocketAddress addr) { ++ localAddress = addr; ++} ++ ++/** ++ * Sets the local address to bind to when sending messages. A random port ++ * will be used. ++ * @param addr The local address to send messages from. ++ */ ++public void ++setLocalAddress(InetAddress addr) { ++ localAddress = new InetSocketAddress(addr, 0); ++} ++ ++public void ++setTCP(boolean flag) { ++ this.useTCP = flag; ++} ++ ++public void ++setIgnoreTruncation(boolean flag) { ++ this.ignoreTruncation = flag; ++} ++ ++public void ++setEDNS(int level, int payloadSize, int flags, List options) { ++ if (level != 0 && level != -1) ++ throw new IllegalArgumentException("invalid EDNS level - " + ++ "must be 0 or -1"); ++ if (payloadSize == 0) ++ payloadSize = DEFAULT_EDNS_PAYLOADSIZE; ++ queryOPT = new OPTRecord(payloadSize, 0, level, flags, options); ++} ++ ++public void ++setEDNS(int level) { ++ setEDNS(level, 0, 0, null); ++} ++ ++public void ++setTSIGKey(TSIG key) { ++ tsig = key; ++} ++ ++TSIG ++getTSIGKey() { ++ return tsig; ++} ++ ++public void ++setTimeout(int secs, int msecs) { ++ timeoutValue = (long)secs * 1000 + msecs; ++} ++ ++public void ++setTimeout(int secs) { ++ setTimeout(secs, 0); ++} ++ ++long ++getTimeout() { ++ return timeoutValue; ++} ++ ++private Message ++parseMessage(byte [] b) throws WireParseException { ++ try { ++ return (new Message(b)); ++ } ++ catch (IOException e) { ++ if (Options.check("verbose")) ++ e.printStackTrace(); ++ if (!(e instanceof WireParseException)) ++ e = new WireParseException("Error parsing message"); ++ throw (WireParseException) e; ++ } ++} ++ ++private void ++verifyTSIG(Message query, Message response, byte [] b, TSIG tsig) { ++ if (tsig == null) ++ return; ++ int error = tsig.verify(response, b, query.getTSIG()); ++ if (Options.check("verbose")) ++ System.err.println("TSIG verify: " + Rcode.TSIGstring(error)); ++} ++ ++private void ++applyEDNS(Message query) { ++ if (queryOPT == null || query.getOPT() != null) ++ return; ++ query.addRecord(queryOPT, Section.ADDITIONAL); ++} ++ ++private int ++maxUDPSize(Message query) { ++ OPTRecord opt = query.getOPT(); ++ if (opt == null) ++ return DEFAULT_UDPSIZE; ++ else ++ return opt.getPayloadSize(); ++} ++ ++/** ++ * Sends a message to a single server and waits for a response. No checking ++ * is done to ensure that the response is associated with the query. ++ * @param query The query to send. ++ * @return The response. ++ * @throws IOException An error occurred while sending or receiving. ++ */ ++public Message ++send(Message query) throws IOException { ++ if (Options.check("verbose")) ++ System.err.println("Sending to " + ++ address.getAddress().getHostAddress() + ++ ":" + address.getPort()); ++ ++ if (query.getHeader().getOpcode() == Opcode.QUERY) { ++ Record question = query.getQuestion(); ++ if (question != null && question.getType() == Type.AXFR) ++ return sendAXFR(query); ++ } ++ ++ query = (Message) query.clone(); ++ applyEDNS(query); ++ if (tsig != null) ++ tsig.apply(query, null); ++ ++ byte [] out = query.toWire(Message.MAXLENGTH); ++ int udpSize = maxUDPSize(query); ++ boolean tcp = false; ++ long endTime = System.currentTimeMillis() + timeoutValue; ++ do { ++ byte [] in; ++ ++ if (useTCP || out.length > udpSize) ++ tcp = true; ++ if (tcp) ++ in = TCPClient.sendrecv(localAddress, address, out, ++ endTime); ++ else ++ in = UDPClient.sendrecv(localAddress, address, out, ++ udpSize, endTime); ++ ++ /* ++ * Check that the response is long enough. ++ */ ++ if (in.length < Header.LENGTH) { ++ throw new WireParseException("invalid DNS header - " + ++ "too short"); ++ } ++ /* ++ * Check that the response ID matches the query ID. We want ++ * to check this before actually parsing the message, so that ++ * if there's a malformed response that's not ours, it ++ * doesn't confuse us. ++ */ ++ int id = ((in[0] & 0xFF) << 8) + (in[1] & 0xFF); ++ int qid = query.getHeader().getID(); ++ if (id != qid) { ++ String error = "invalid message id: expected " + qid + ++ "; got id " + id; ++ if (tcp) { ++ throw new WireParseException(error); ++ } else { ++ if (Options.check("verbose")) { ++ System.err.println(error); ++ } ++ continue; ++ } ++ } ++ Message response = parseMessage(in); ++ verifyTSIG(query, response, in, tsig); ++ if (!tcp && !ignoreTruncation && ++ response.getHeader().getFlag(Flags.TC)) ++ { ++ tcp = true; ++ continue; ++ } ++ return response; ++ } while (true); ++} ++ ++/** ++ * Asynchronously sends a message to a single server, registering a listener ++ * to receive a callback on success or exception. Multiple asynchronous ++ * lookups can be performed in parallel. Since the callback may be invoked ++ * before the function returns, external synchronization is necessary. ++ * @param query The query to send ++ * @param listener The object containing the callbacks. ++ * @return An identifier, which is also a parameter in the callback ++ */ ++public Object ++sendAsync(final Message query, final ResolverListener listener) { ++ final Object id; ++ synchronized (this) { ++ id = new Integer(uniqueID++); ++ } ++ Record question = query.getQuestion(); ++ String qname; ++ if (question != null) ++ qname = question.getName().toString(); ++ else ++ qname = "(none)"; ++ String name = this.getClass() + ": " + qname; ++ Thread thread = new ResolveThread(this, query, id, listener); ++ thread.setName(name); ++ thread.setDaemon(true); ++ thread.start(); ++ return id; ++} ++ ++private Message ++sendAXFR(Message query) throws IOException { ++ Name qname = query.getQuestion().getName(); ++ ZoneTransferIn xfrin = ZoneTransferIn.newAXFR(qname, address, tsig); ++ xfrin.setTimeout((int)(getTimeout() / 1000)); ++ xfrin.setLocalAddress(localAddress); ++ try { ++ xfrin.run(); ++ } ++ catch (ZoneTransferException e) { ++ throw new WireParseException(e.getMessage()); ++ } ++ List records = xfrin.getAXFR(); ++ Message response = new Message(query.getHeader().getID()); ++ response.getHeader().setFlag(Flags.AA); ++ response.getHeader().setFlag(Flags.QR); ++ response.addRecord(query.getQuestion(), Section.QUESTION); ++ Iterator it = records.iterator(); ++ while (it.hasNext()) ++ response.addRecord((Record)it.next(), Section.ANSWER); ++ return response; ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SingleCompressedNameBase.java b/external/asmack/build/src/trunk/org/xbill/DNS/SingleCompressedNameBase.java +new file mode 100644 +index 0000000..790ca1f +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/SingleCompressedNameBase.java +@@ -0,0 +1,31 @@ ++// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Implements common functionality for the many record types whose format ++ * is a single compressed name. ++ * ++ * @author Brian Wellington ++ */ ++ ++abstract class SingleCompressedNameBase extends SingleNameBase { ++ ++private static final long serialVersionUID = -236435396815460677L; ++ ++protected ++SingleCompressedNameBase() {} ++ ++protected ++SingleCompressedNameBase(Name name, int type, int dclass, long ttl, ++ Name singleName, String description) ++{ ++ super(name, type, dclass, ttl, singleName, description); ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ singleName.toWire(out, c, canonical); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SingleNameBase.java b/external/asmack/build/src/trunk/org/xbill/DNS/SingleNameBase.java +new file mode 100644 +index 0000000..6d6c041 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/SingleNameBase.java +@@ -0,0 +1,61 @@ ++// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++/** ++ * Implements common functionality for the many record types whose format ++ * is a single name. ++ * ++ * @author Brian Wellington ++ */ ++ ++abstract class SingleNameBase extends Record { ++ ++private static final long serialVersionUID = -18595042501413L; ++ ++protected Name singleName; ++ ++protected ++SingleNameBase() {} ++ ++protected ++SingleNameBase(Name name, int type, int dclass, long ttl) { ++ super(name, type, dclass, ttl); ++} ++ ++protected ++SingleNameBase(Name name, int type, int dclass, long ttl, Name singleName, ++ String description) ++{ ++ super(name, type, dclass, ttl); ++ this.singleName = checkName(description, singleName); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ singleName = new Name(in); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ singleName = st.getName(origin); ++} ++ ++String ++rrToString() { ++ return singleName.toString(); ++} ++ ++protected Name ++getSingleName() { ++ return singleName; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ singleName.toWire(out, null, canonical); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/TCPClient.java b/external/asmack/build/src/trunk/org/xbill/DNS/TCPClient.java +new file mode 100644 +index 0000000..1f17d72 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/TCPClient.java +@@ -0,0 +1,132 @@ ++// Copyright (c) 2005 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.net.*; ++import java.nio.*; ++import java.nio.channels.*; ++ ++final class TCPClient extends Client { ++ ++public ++TCPClient(long endTime) throws IOException { ++ super(SocketChannel.open(), endTime); ++} ++ ++void ++bind(SocketAddress addr) throws IOException { ++ SocketChannel channel = (SocketChannel) key.channel(); ++ channel.socket().bind(addr); ++} ++ ++void ++connect(SocketAddress addr) throws IOException { ++ SocketChannel channel = (SocketChannel) key.channel(); ++ if (channel.connect(addr)) ++ return; ++ key.interestOps(SelectionKey.OP_CONNECT); ++ try { ++ while (!channel.finishConnect()) { ++ if (!key.isConnectable()) ++ blockUntil(key, endTime); ++ } ++ } ++ finally { ++ if (key.isValid()) ++ key.interestOps(0); ++ } ++} ++ ++void ++send(byte [] data) throws IOException { ++ SocketChannel channel = (SocketChannel) key.channel(); ++ verboseLog("TCP write", data); ++ byte [] lengthArray = new byte[2]; ++ lengthArray[0] = (byte)(data.length >>> 8); ++ lengthArray[1] = (byte)(data.length & 0xFF); ++ ByteBuffer [] buffers = new ByteBuffer[2]; ++ buffers[0] = ByteBuffer.wrap(lengthArray); ++ buffers[1] = ByteBuffer.wrap(data); ++ int nsent = 0; ++ key.interestOps(SelectionKey.OP_WRITE); ++ try { ++ while (nsent < data.length + 2) { ++ if (key.isWritable()) { ++ long n = channel.write(buffers); ++ if (n < 0) ++ throw new EOFException(); ++ nsent += (int) n; ++ if (nsent < data.length + 2 && ++ System.currentTimeMillis() > endTime) ++ throw new SocketTimeoutException(); ++ } else ++ blockUntil(key, endTime); ++ } ++ } ++ finally { ++ if (key.isValid()) ++ key.interestOps(0); ++ } ++} ++ ++private byte [] ++_recv(int length) throws IOException { ++ SocketChannel channel = (SocketChannel) key.channel(); ++ int nrecvd = 0; ++ byte [] data = new byte[length]; ++ ByteBuffer buffer = ByteBuffer.wrap(data); ++ key.interestOps(SelectionKey.OP_READ); ++ try { ++ while (nrecvd < length) { ++ if (key.isReadable()) { ++ long n = channel.read(buffer); ++ if (n < 0) ++ throw new EOFException(); ++ nrecvd += (int) n; ++ if (nrecvd < length && ++ System.currentTimeMillis() > endTime) ++ throw new SocketTimeoutException(); ++ } else ++ blockUntil(key, endTime); ++ } ++ } ++ finally { ++ if (key.isValid()) ++ key.interestOps(0); ++ } ++ return data; ++} ++ ++byte [] ++recv() throws IOException { ++ byte [] buf = _recv(2); ++ int length = ((buf[0] & 0xFF) << 8) + (buf[1] & 0xFF); ++ byte [] data = _recv(length); ++ verboseLog("TCP read", data); ++ return data; ++} ++ ++static byte [] ++sendrecv(SocketAddress local, SocketAddress remote, byte [] data, long endTime) ++throws IOException ++{ ++ TCPClient client = new TCPClient(endTime); ++ try { ++ if (local != null) ++ client.bind(local); ++ client.connect(remote); ++ client.send(data); ++ return client.recv(); ++ } ++ finally { ++ client.cleanup(); ++ } ++} ++ ++static byte [] ++sendrecv(SocketAddress addr, byte [] data, long endTime) throws IOException { ++ return sendrecv(null, addr, data, endTime); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/TKEYRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/TKEYRecord.java +new file mode 100644 +index 0000000..4dcbb5c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/TKEYRecord.java +@@ -0,0 +1,225 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.util.*; ++import org.xbill.DNS.utils.*; ++ ++/** ++ * Transaction Key - used to compute and/or securely transport a shared ++ * secret to be used with TSIG. ++ * @see TSIG ++ * ++ * @author Brian Wellington ++ */ ++ ++public class TKEYRecord extends Record { ++ ++private static final long serialVersionUID = 8828458121926391756L; ++ ++private Name alg; ++private Date timeInception; ++private Date timeExpire; ++private int mode, error; ++private byte [] key; ++private byte [] other; ++ ++/** The key is assigned by the server (unimplemented) */ ++public static final int SERVERASSIGNED = 1; ++ ++/** The key is computed using a Diffie-Hellman key exchange */ ++public static final int DIFFIEHELLMAN = 2; ++ ++/** The key is computed using GSS_API (unimplemented) */ ++public static final int GSSAPI = 3; ++ ++/** The key is assigned by the resolver (unimplemented) */ ++public static final int RESOLVERASSIGNED = 4; ++ ++/** The key should be deleted */ ++public static final int DELETE = 5; ++ ++TKEYRecord() {} ++ ++Record ++getObject() { ++ return new TKEYRecord(); ++} ++ ++/** ++ * Creates a TKEY Record from the given data. ++ * @param alg The shared key's algorithm ++ * @param timeInception The beginning of the validity period of the shared ++ * secret or keying material ++ * @param timeExpire The end of the validity period of the shared ++ * secret or keying material ++ * @param mode The mode of key agreement ++ * @param error The extended error field. Should be 0 in queries ++ * @param key The shared secret ++ * @param other The other data field. Currently unused ++ * responses. ++ */ ++public ++TKEYRecord(Name name, int dclass, long ttl, Name alg, ++ Date timeInception, Date timeExpire, int mode, int error, ++ byte [] key, byte other[]) ++{ ++ super(name, Type.TKEY, dclass, ttl); ++ this.alg = checkName("alg", alg); ++ this.timeInception = timeInception; ++ this.timeExpire = timeExpire; ++ this.mode = checkU16("mode", mode); ++ this.error = checkU16("error", error); ++ this.key = key; ++ this.other = other; ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ alg = new Name(in); ++ timeInception = new Date(1000 * in.readU32()); ++ timeExpire = new Date(1000 * in.readU32()); ++ mode = in.readU16(); ++ error = in.readU16(); ++ ++ int keylen = in.readU16(); ++ if (keylen > 0) ++ key = in.readByteArray(keylen); ++ else ++ key = null; ++ ++ int otherlen = in.readU16(); ++ if (otherlen > 0) ++ other = in.readByteArray(otherlen); ++ else ++ other = null; ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ throw st.exception("no text format defined for TKEY"); ++} ++ ++protected String ++modeString() { ++ switch (mode) { ++ case SERVERASSIGNED: return "SERVERASSIGNED"; ++ case DIFFIEHELLMAN: return "DIFFIEHELLMAN"; ++ case GSSAPI: return "GSSAPI"; ++ case RESOLVERASSIGNED: return "RESOLVERASSIGNED"; ++ case DELETE: return "DELETE"; ++ default: return Integer.toString(mode); ++ } ++} ++ ++/** Converts rdata to a String */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(alg); ++ sb.append(" "); ++ if (Options.check("multiline")) ++ sb.append("(\n\t"); ++ sb.append(FormattedTime.format(timeInception)); ++ sb.append(" "); ++ sb.append(FormattedTime.format(timeExpire)); ++ sb.append(" "); ++ sb.append(modeString()); ++ sb.append(" "); ++ sb.append(Rcode.TSIGstring(error)); ++ if (Options.check("multiline")) { ++ sb.append("\n"); ++ if (key != null) { ++ sb.append(base64.formatString(key, 64, "\t", false)); ++ sb.append("\n"); ++ } ++ if (other != null) ++ sb.append(base64.formatString(other, 64, "\t", false)); ++ sb.append(" )"); ++ } else { ++ sb.append(" "); ++ if (key != null) { ++ sb.append(base64.toString(key)); ++ sb.append(" "); ++ } ++ if (other != null) ++ sb.append(base64.toString(other)); ++ } ++ return sb.toString(); ++} ++ ++/** Returns the shared key's algorithm */ ++public Name ++getAlgorithm() { ++ return alg; ++} ++ ++/** ++ * Returns the beginning of the validity period of the shared secret or ++ * keying material ++ */ ++public Date ++getTimeInception() { ++ return timeInception; ++} ++ ++/** ++ * Returns the end of the validity period of the shared secret or ++ * keying material ++ */ ++public Date ++getTimeExpire() { ++ return timeExpire; ++} ++ ++/** Returns the key agreement mode */ ++public int ++getMode() { ++ return mode; ++} ++ ++/** Returns the extended error */ ++public int ++getError() { ++ return error; ++} ++ ++/** Returns the shared secret or keying material */ ++public byte [] ++getKey() { ++ return key; ++} ++ ++/** Returns the other data */ ++public byte [] ++getOther() { ++ return other; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ alg.toWire(out, null, canonical); ++ ++ out.writeU32(timeInception.getTime() / 1000); ++ out.writeU32(timeExpire.getTime() / 1000); ++ ++ out.writeU16(mode); ++ out.writeU16(error); ++ ++ if (key != null) { ++ out.writeU16(key.length); ++ out.writeByteArray(key); ++ } ++ else ++ out.writeU16(0); ++ ++ if (other != null) { ++ out.writeU16(other.length); ++ out.writeByteArray(other); ++ } ++ else ++ out.writeU16(0); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/TSIG.java b/external/asmack/build/src/trunk/org/xbill/DNS/TSIG.java +new file mode 100644 +index 0000000..d9e6972 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/TSIG.java +@@ -0,0 +1,592 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.util.*; ++import org.xbill.DNS.utils.*; ++ ++/** ++ * Transaction signature handling. This class generates and verifies ++ * TSIG records on messages, which provide transaction security. ++ * @see TSIGRecord ++ * ++ * @author Brian Wellington ++ */ ++ ++public class TSIG { ++ ++private static final String HMAC_MD5_STR = "HMAC-MD5.SIG-ALG.REG.INT."; ++private static final String HMAC_SHA1_STR = "hmac-sha1."; ++private static final String HMAC_SHA224_STR = "hmac-sha224."; ++private static final String HMAC_SHA256_STR = "hmac-sha256."; ++private static final String HMAC_SHA384_STR = "hmac-sha384."; ++private static final String HMAC_SHA512_STR = "hmac-sha512."; ++ ++/** The domain name representing the HMAC-MD5 algorithm. */ ++public static final Name HMAC_MD5 = Name.fromConstantString(HMAC_MD5_STR); ++ ++/** The domain name representing the HMAC-MD5 algorithm (deprecated). */ ++public static final Name HMAC = HMAC_MD5; ++ ++/** The domain name representing the HMAC-SHA1 algorithm. */ ++public static final Name HMAC_SHA1 = Name.fromConstantString(HMAC_SHA1_STR); ++ ++/** ++ * The domain name representing the HMAC-SHA224 algorithm. ++ * Note that SHA224 is not supported by Java out-of-the-box, this requires use ++ * of a third party provider like BouncyCastle.org. ++ */ ++public static final Name HMAC_SHA224 = Name.fromConstantString(HMAC_SHA224_STR); ++ ++/** The domain name representing the HMAC-SHA256 algorithm. */ ++public static final Name HMAC_SHA256 = Name.fromConstantString(HMAC_SHA256_STR); ++ ++/** The domain name representing the HMAC-SHA384 algorithm. */ ++public static final Name HMAC_SHA384 = Name.fromConstantString(HMAC_SHA384_STR); ++ ++/** The domain name representing the HMAC-SHA512 algorithm. */ ++public static final Name HMAC_SHA512 = Name.fromConstantString(HMAC_SHA512_STR); ++ ++/** ++ * The default fudge value for outgoing packets. Can be overriden by the ++ * tsigfudge option. ++ */ ++public static final short FUDGE = 300; ++ ++private Name name, alg; ++private String digest; ++private int digestBlockLength; ++private byte [] key; ++ ++private void ++getDigest() { ++ if (alg.equals(HMAC_MD5)) { ++ digest = "md5"; ++ digestBlockLength = 64; ++ } else if (alg.equals(HMAC_SHA1)) { ++ digest = "sha-1"; ++ digestBlockLength = 64; ++ } else if (alg.equals(HMAC_SHA224)) { ++ digest = "sha-224"; ++ digestBlockLength = 64; ++ } else if (alg.equals(HMAC_SHA256)) { ++ digest = "sha-256"; ++ digestBlockLength = 64; ++ } else if (alg.equals(HMAC_SHA512)) { ++ digest = "sha-512"; ++ digestBlockLength = 128; ++ } else if (alg.equals(HMAC_SHA384)) { ++ digest = "sha-384"; ++ digestBlockLength = 128; ++ } else ++ throw new IllegalArgumentException("Invalid algorithm"); ++} ++ ++/** ++ * Creates a new TSIG key, which can be used to sign or verify a message. ++ * @param algorithm The algorithm of the shared key. ++ * @param name The name of the shared key. ++ * @param key The shared key's data. ++ */ ++public ++TSIG(Name algorithm, Name name, byte [] key) { ++ this.name = name; ++ this.alg = algorithm; ++ this.key = key; ++ getDigest(); ++} ++ ++/** ++ * Creates a new TSIG key with the hmac-md5 algorithm, which can be used to ++ * sign or verify a message. ++ * @param name The name of the shared key. ++ * @param key The shared key's data. ++ */ ++public ++TSIG(Name name, byte [] key) { ++ this(HMAC_MD5, name, key); ++} ++ ++/** ++ * Creates a new TSIG object, which can be used to sign or verify a message. ++ * @param name The name of the shared key. ++ * @param key The shared key's data represented as a base64 encoded string. ++ * @throws IllegalArgumentException The key name is an invalid name ++ * @throws IllegalArgumentException The key data is improperly encoded ++ */ ++public ++TSIG(Name algorithm, String name, String key) { ++ this.key = base64.fromString(key); ++ if (this.key == null) ++ throw new IllegalArgumentException("Invalid TSIG key string"); ++ try { ++ this.name = Name.fromString(name, Name.root); ++ } ++ catch (TextParseException e) { ++ throw new IllegalArgumentException("Invalid TSIG key name"); ++ } ++ this.alg = algorithm; ++ getDigest(); ++} ++ ++/** ++ * Creates a new TSIG object, which can be used to sign or verify a message. ++ * @param name The name of the shared key. ++ * @param algorithm The algorithm of the shared key. The legal values are ++ * "hmac-md5", "hmac-sha1", "hmac-sha224", "hmac-sha256", "hmac-sha384", and ++ * "hmac-sha512". ++ * @param key The shared key's data represented as a base64 encoded string. ++ * @throws IllegalArgumentException The key name is an invalid name ++ * @throws IllegalArgumentException The key data is improperly encoded ++ */ ++public ++TSIG(String algorithm, String name, String key) { ++ this(HMAC_MD5, name, key); ++ if (algorithm.equalsIgnoreCase("hmac-md5")) ++ this.alg = HMAC_MD5; ++ else if (algorithm.equalsIgnoreCase("hmac-sha1")) ++ this.alg = HMAC_SHA1; ++ else if (algorithm.equalsIgnoreCase("hmac-sha224")) ++ this.alg = HMAC_SHA224; ++ else if (algorithm.equalsIgnoreCase("hmac-sha256")) ++ this.alg = HMAC_SHA256; ++ else if (algorithm.equalsIgnoreCase("hmac-sha384")) ++ this.alg = HMAC_SHA384; ++ else if (algorithm.equalsIgnoreCase("hmac-sha512")) ++ this.alg = HMAC_SHA512; ++ else ++ throw new IllegalArgumentException("Invalid TSIG algorithm"); ++ getDigest(); ++} ++ ++/** ++ * Creates a new TSIG object with the hmac-md5 algorithm, which can be used to ++ * sign or verify a message. ++ * @param name The name of the shared key ++ * @param key The shared key's data, represented as a base64 encoded string. ++ * @throws IllegalArgumentException The key name is an invalid name ++ * @throws IllegalArgumentException The key data is improperly encoded ++ */ ++public ++TSIG(String name, String key) { ++ this(HMAC_MD5, name, key); ++} ++ ++/** ++ * Creates a new TSIG object, which can be used to sign or verify a message. ++ * @param str The TSIG key, in the form name:secret, name/secret, ++ * alg:name:secret, or alg/name/secret. If an algorithm is specified, it must ++ * be "hmac-md5", "hmac-sha1", or "hmac-sha256". ++ * @throws IllegalArgumentException The string does not contain both a name ++ * and secret. ++ * @throws IllegalArgumentException The key name is an invalid name ++ * @throws IllegalArgumentException The key data is improperly encoded ++ */ ++static public TSIG ++fromString(String str) { ++ String [] parts = str.split("[:/]", 3); ++ if (parts.length < 2) ++ throw new IllegalArgumentException("Invalid TSIG key " + ++ "specification"); ++ if (parts.length == 3) { ++ try { ++ return new TSIG(parts[0], parts[1], parts[2]); ++ } catch (IllegalArgumentException e) { ++ parts = str.split("[:/]", 2); ++ } ++ } ++ return new TSIG(HMAC_MD5, parts[0], parts[1]); ++} ++ ++/** ++ * Generates a TSIG record with a specific error for a message that has ++ * been rendered. ++ * @param m The message ++ * @param b The rendered message ++ * @param error The error ++ * @param old If this message is a response, the TSIG from the request ++ * @return The TSIG record to be added to the message ++ */ ++public TSIGRecord ++generate(Message m, byte [] b, int error, TSIGRecord old) { ++ Date timeSigned; ++ if (error != Rcode.BADTIME) ++ timeSigned = new Date(); ++ else ++ timeSigned = old.getTimeSigned(); ++ int fudge; ++ HMAC hmac = null; ++ if (error == Rcode.NOERROR || error == Rcode.BADTIME) ++ hmac = new HMAC(digest, digestBlockLength, key); ++ ++ fudge = Options.intValue("tsigfudge"); ++ if (fudge < 0 || fudge > 0x7FFF) ++ fudge = FUDGE; ++ ++ if (old != null) { ++ DNSOutput out = new DNSOutput(); ++ out.writeU16(old.getSignature().length); ++ if (hmac != null) { ++ hmac.update(out.toByteArray()); ++ hmac.update(old.getSignature()); ++ } ++ } ++ ++ /* Digest the message */ ++ if (hmac != null) ++ hmac.update(b); ++ ++ DNSOutput out = new DNSOutput(); ++ name.toWireCanonical(out); ++ out.writeU16(DClass.ANY); /* class */ ++ out.writeU32(0); /* ttl */ ++ alg.toWireCanonical(out); ++ long time = timeSigned.getTime() / 1000; ++ int timeHigh = (int) (time >> 32); ++ long timeLow = (time & 0xFFFFFFFFL); ++ out.writeU16(timeHigh); ++ out.writeU32(timeLow); ++ out.writeU16(fudge); ++ ++ out.writeU16(error); ++ out.writeU16(0); /* No other data */ ++ ++ if (hmac != null) ++ hmac.update(out.toByteArray()); ++ ++ byte [] signature; ++ if (hmac != null) ++ signature = hmac.sign(); ++ else ++ signature = new byte[0]; ++ ++ byte [] other = null; ++ if (error == Rcode.BADTIME) { ++ out = new DNSOutput(); ++ time = new Date().getTime() / 1000; ++ timeHigh = (int) (time >> 32); ++ timeLow = (time & 0xFFFFFFFFL); ++ out.writeU16(timeHigh); ++ out.writeU32(timeLow); ++ other = out.toByteArray(); ++ } ++ ++ return (new TSIGRecord(name, DClass.ANY, 0, alg, timeSigned, fudge, ++ signature, m.getHeader().getID(), error, other)); ++} ++ ++/** ++ * Generates a TSIG record with a specific error for a message and adds it ++ * to the message. ++ * @param m The message ++ * @param error The error ++ * @param old If this message is a response, the TSIG from the request ++ */ ++public void ++apply(Message m, int error, TSIGRecord old) { ++ Record r = generate(m, m.toWire(), error, old); ++ m.addRecord(r, Section.ADDITIONAL); ++ m.tsigState = Message.TSIG_SIGNED; ++} ++ ++/** ++ * Generates a TSIG record for a message and adds it to the message ++ * @param m The message ++ * @param old If this message is a response, the TSIG from the request ++ */ ++public void ++apply(Message m, TSIGRecord old) { ++ apply(m, Rcode.NOERROR, old); ++} ++ ++/** ++ * Generates a TSIG record for a message and adds it to the message ++ * @param m The message ++ * @param old If this message is a response, the TSIG from the request ++ */ ++public void ++applyStream(Message m, TSIGRecord old, boolean first) { ++ if (first) { ++ apply(m, old); ++ return; ++ } ++ Date timeSigned = new Date(); ++ int fudge; ++ HMAC hmac = new HMAC(digest, digestBlockLength, key); ++ ++ fudge = Options.intValue("tsigfudge"); ++ if (fudge < 0 || fudge > 0x7FFF) ++ fudge = FUDGE; ++ ++ DNSOutput out = new DNSOutput(); ++ out.writeU16(old.getSignature().length); ++ hmac.update(out.toByteArray()); ++ hmac.update(old.getSignature()); ++ ++ /* Digest the message */ ++ hmac.update(m.toWire()); ++ ++ out = new DNSOutput(); ++ long time = timeSigned.getTime() / 1000; ++ int timeHigh = (int) (time >> 32); ++ long timeLow = (time & 0xFFFFFFFFL); ++ out.writeU16(timeHigh); ++ out.writeU32(timeLow); ++ out.writeU16(fudge); ++ ++ hmac.update(out.toByteArray()); ++ ++ byte [] signature = hmac.sign(); ++ byte [] other = null; ++ ++ Record r = new TSIGRecord(name, DClass.ANY, 0, alg, timeSigned, fudge, ++ signature, m.getHeader().getID(), ++ Rcode.NOERROR, other); ++ m.addRecord(r, Section.ADDITIONAL); ++ m.tsigState = Message.TSIG_SIGNED; ++} ++ ++/** ++ * Verifies a TSIG record on an incoming message. Since this is only called ++ * in the context where a TSIG is expected to be present, it is an error ++ * if one is not present. After calling this routine, Message.isVerified() may ++ * be called on this message. ++ * @param m The message ++ * @param b An array containing the message in unparsed form. This is ++ * necessary since TSIG signs the message in wire format, and we can't ++ * recreate the exact wire format (with the same name compression). ++ * @param length The length of the message in the array. ++ * @param old If this message is a response, the TSIG from the request ++ * @return The result of the verification (as an Rcode) ++ * @see Rcode ++ */ ++public byte ++verify(Message m, byte [] b, int length, TSIGRecord old) { ++ m.tsigState = Message.TSIG_FAILED; ++ TSIGRecord tsig = m.getTSIG(); ++ HMAC hmac = new HMAC(digest, digestBlockLength, key); ++ if (tsig == null) ++ return Rcode.FORMERR; ++ ++ if (!tsig.getName().equals(name) || !tsig.getAlgorithm().equals(alg)) { ++ if (Options.check("verbose")) ++ System.err.println("BADKEY failure"); ++ return Rcode.BADKEY; ++ } ++ long now = System.currentTimeMillis(); ++ long then = tsig.getTimeSigned().getTime(); ++ long fudge = tsig.getFudge(); ++ if (Math.abs(now - then) > fudge * 1000) { ++ if (Options.check("verbose")) ++ System.err.println("BADTIME failure"); ++ return Rcode.BADTIME; ++ } ++ ++ if (old != null && tsig.getError() != Rcode.BADKEY && ++ tsig.getError() != Rcode.BADSIG) ++ { ++ DNSOutput out = new DNSOutput(); ++ out.writeU16(old.getSignature().length); ++ hmac.update(out.toByteArray()); ++ hmac.update(old.getSignature()); ++ } ++ m.getHeader().decCount(Section.ADDITIONAL); ++ byte [] header = m.getHeader().toWire(); ++ m.getHeader().incCount(Section.ADDITIONAL); ++ hmac.update(header); ++ ++ int len = m.tsigstart - header.length; ++ hmac.update(b, header.length, len); ++ ++ DNSOutput out = new DNSOutput(); ++ tsig.getName().toWireCanonical(out); ++ out.writeU16(tsig.dclass); ++ out.writeU32(tsig.ttl); ++ tsig.getAlgorithm().toWireCanonical(out); ++ long time = tsig.getTimeSigned().getTime() / 1000; ++ int timeHigh = (int) (time >> 32); ++ long timeLow = (time & 0xFFFFFFFFL); ++ out.writeU16(timeHigh); ++ out.writeU32(timeLow); ++ out.writeU16(tsig.getFudge()); ++ out.writeU16(tsig.getError()); ++ if (tsig.getOther() != null) { ++ out.writeU16(tsig.getOther().length); ++ out.writeByteArray(tsig.getOther()); ++ } else { ++ out.writeU16(0); ++ } ++ ++ hmac.update(out.toByteArray()); ++ ++ byte [] signature = tsig.getSignature(); ++ int digestLength = hmac.digestLength(); ++ int minDigestLength = digest.equals("md5") ? 10 : digestLength / 2; ++ ++ if (signature.length > digestLength) { ++ if (Options.check("verbose")) ++ System.err.println("BADSIG: signature too long"); ++ return Rcode.BADSIG; ++ } else if (signature.length < minDigestLength) { ++ if (Options.check("verbose")) ++ System.err.println("BADSIG: signature too short"); ++ return Rcode.BADSIG; ++ } else if (!hmac.verify(signature, true)) { ++ if (Options.check("verbose")) ++ System.err.println("BADSIG: signature verification"); ++ return Rcode.BADSIG; ++ } ++ ++ m.tsigState = Message.TSIG_VERIFIED; ++ return Rcode.NOERROR; ++} ++ ++/** ++ * Verifies a TSIG record on an incoming message. Since this is only called ++ * in the context where a TSIG is expected to be present, it is an error ++ * if one is not present. After calling this routine, Message.isVerified() may ++ * be called on this message. ++ * @param m The message ++ * @param b The message in unparsed form. This is necessary since TSIG ++ * signs the message in wire format, and we can't recreate the exact wire ++ * format (with the same name compression). ++ * @param old If this message is a response, the TSIG from the request ++ * @return The result of the verification (as an Rcode) ++ * @see Rcode ++ */ ++public int ++verify(Message m, byte [] b, TSIGRecord old) { ++ return verify(m, b, b.length, old); ++} ++ ++/** ++ * Returns the maximum length of a TSIG record generated by this key. ++ * @see TSIGRecord ++ */ ++public int ++recordLength() { ++ return (name.length() + 10 + ++ alg.length() + ++ 8 + // time signed, fudge ++ 18 + // 2 byte MAC length, 16 byte MAC ++ 4 + // original id, error ++ 8); // 2 byte error length, 6 byte max error field. ++} ++ ++public static class StreamVerifier { ++ /** ++ * A helper class for verifying multiple message responses. ++ */ ++ ++ private TSIG key; ++ private HMAC verifier; ++ private int nresponses; ++ private int lastsigned; ++ private TSIGRecord lastTSIG; ++ ++ /** Creates an object to verify a multiple message response */ ++ public ++ StreamVerifier(TSIG tsig, TSIGRecord old) { ++ key = tsig; ++ verifier = new HMAC(key.digest, key.digestBlockLength, key.key); ++ nresponses = 0; ++ lastTSIG = old; ++ } ++ ++ /** ++ * Verifies a TSIG record on an incoming message that is part of a ++ * multiple message response. ++ * TSIG records must be present on the first and last messages, and ++ * at least every 100 records in between. ++ * After calling this routine, Message.isVerified() may be called on ++ * this message. ++ * @param m The message ++ * @param b The message in unparsed form ++ * @return The result of the verification (as an Rcode) ++ * @see Rcode ++ */ ++ public int ++ verify(Message m, byte [] b) { ++ TSIGRecord tsig = m.getTSIG(); ++ ++ nresponses++; ++ ++ if (nresponses == 1) { ++ int result = key.verify(m, b, lastTSIG); ++ if (result == Rcode.NOERROR) { ++ byte [] signature = tsig.getSignature(); ++ DNSOutput out = new DNSOutput(); ++ out.writeU16(signature.length); ++ verifier.update(out.toByteArray()); ++ verifier.update(signature); ++ } ++ lastTSIG = tsig; ++ return result; ++ } ++ ++ if (tsig != null) ++ m.getHeader().decCount(Section.ADDITIONAL); ++ byte [] header = m.getHeader().toWire(); ++ if (tsig != null) ++ m.getHeader().incCount(Section.ADDITIONAL); ++ verifier.update(header); ++ ++ int len; ++ if (tsig == null) ++ len = b.length - header.length; ++ else ++ len = m.tsigstart - header.length; ++ verifier.update(b, header.length, len); ++ ++ if (tsig != null) { ++ lastsigned = nresponses; ++ lastTSIG = tsig; ++ } ++ else { ++ boolean required = (nresponses - lastsigned >= 100); ++ if (required) { ++ m.tsigState = Message.TSIG_FAILED; ++ return Rcode.FORMERR; ++ } else { ++ m.tsigState = Message.TSIG_INTERMEDIATE; ++ return Rcode.NOERROR; ++ } ++ } ++ ++ if (!tsig.getName().equals(key.name) || ++ !tsig.getAlgorithm().equals(key.alg)) ++ { ++ if (Options.check("verbose")) ++ System.err.println("BADKEY failure"); ++ m.tsigState = Message.TSIG_FAILED; ++ return Rcode.BADKEY; ++ } ++ ++ DNSOutput out = new DNSOutput(); ++ long time = tsig.getTimeSigned().getTime() / 1000; ++ int timeHigh = (int) (time >> 32); ++ long timeLow = (time & 0xFFFFFFFFL); ++ out.writeU16(timeHigh); ++ out.writeU32(timeLow); ++ out.writeU16(tsig.getFudge()); ++ verifier.update(out.toByteArray()); ++ ++ if (verifier.verify(tsig.getSignature()) == false) { ++ if (Options.check("verbose")) ++ System.err.println("BADSIG failure"); ++ m.tsigState = Message.TSIG_FAILED; ++ return Rcode.BADSIG; ++ } ++ ++ verifier.clear(); ++ out = new DNSOutput(); ++ out.writeU16(tsig.getSignature().length); ++ verifier.update(out.toByteArray()); ++ verifier.update(tsig.getSignature()); ++ ++ m.tsigState = Message.TSIG_VERIFIED; ++ return Rcode.NOERROR; ++ } ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/TSIGRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/TSIGRecord.java +new file mode 100644 +index 0000000..c7ce9ed +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/TSIGRecord.java +@@ -0,0 +1,220 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.util.*; ++import org.xbill.DNS.utils.*; ++ ++/** ++ * Transaction Signature - this record is automatically generated by the ++ * resolver. TSIG records provide transaction security between the ++ * sender and receiver of a message, using a shared key. ++ * @see Resolver ++ * @see TSIG ++ * ++ * @author Brian Wellington ++ */ ++ ++public class TSIGRecord extends Record { ++ ++private static final long serialVersionUID = -88820909016649306L; ++ ++private Name alg; ++private Date timeSigned; ++private int fudge; ++private byte [] signature; ++private int originalID; ++private int error; ++private byte [] other; ++ ++TSIGRecord() {} ++ ++Record ++getObject() { ++ return new TSIGRecord(); ++} ++ ++/** ++ * Creates a TSIG Record from the given data. This is normally called by ++ * the TSIG class ++ * @param alg The shared key's algorithm ++ * @param timeSigned The time that this record was generated ++ * @param fudge The fudge factor for time - if the time that the message is ++ * received is not in the range [now - fudge, now + fudge], the signature ++ * fails ++ * @param signature The signature ++ * @param originalID The message ID at the time of its generation ++ * @param error The extended error field. Should be 0 in queries. ++ * @param other The other data field. Currently used only in BADTIME ++ * responses. ++ * @see TSIG ++ */ ++public ++TSIGRecord(Name name, int dclass, long ttl, Name alg, Date timeSigned, ++ int fudge, byte [] signature, int originalID, int error, ++ byte other[]) ++{ ++ super(name, Type.TSIG, dclass, ttl); ++ this.alg = checkName("alg", alg); ++ this.timeSigned = timeSigned; ++ this.fudge = checkU16("fudge", fudge); ++ this.signature = signature; ++ this.originalID = checkU16("originalID", originalID); ++ this.error = checkU16("error", error); ++ this.other = other; ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ alg = new Name(in); ++ ++ long timeHigh = in.readU16(); ++ long timeLow = in.readU32(); ++ long time = (timeHigh << 32) + timeLow; ++ timeSigned = new Date(time * 1000); ++ fudge = in.readU16(); ++ ++ int sigLen = in.readU16(); ++ signature = in.readByteArray(sigLen); ++ ++ originalID = in.readU16(); ++ error = in.readU16(); ++ ++ int otherLen = in.readU16(); ++ if (otherLen > 0) ++ other = in.readByteArray(otherLen); ++ else ++ other = null; ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ throw st.exception("no text format defined for TSIG"); ++} ++ ++/** Converts rdata to a String */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(alg); ++ sb.append(" "); ++ if (Options.check("multiline")) ++ sb.append("(\n\t"); ++ ++ sb.append (timeSigned.getTime() / 1000); ++ sb.append (" "); ++ sb.append (fudge); ++ sb.append (" "); ++ sb.append (signature.length); ++ if (Options.check("multiline")) { ++ sb.append ("\n"); ++ sb.append (base64.formatString(signature, 64, "\t", false)); ++ } else { ++ sb.append (" "); ++ sb.append (base64.toString(signature)); ++ } ++ sb.append (" "); ++ sb.append (Rcode.TSIGstring(error)); ++ sb.append (" "); ++ if (other == null) ++ sb.append (0); ++ else { ++ sb.append (other.length); ++ if (Options.check("multiline")) ++ sb.append("\n\n\n\t"); ++ else ++ sb.append(" "); ++ if (error == Rcode.BADTIME) { ++ if (other.length != 6) { ++ sb.append(""); ++ } else { ++ long time = ((long)(other[0] & 0xFF) << 40) + ++ ((long)(other[1] & 0xFF) << 32) + ++ ((other[2] & 0xFF) << 24) + ++ ((other[3] & 0xFF) << 16) + ++ ((other[4] & 0xFF) << 8) + ++ ((other[5] & 0xFF) ); ++ sb.append(""); ++ } ++ } else { ++ sb.append("<"); ++ sb.append(base64.toString(other)); ++ sb.append(">"); ++ } ++ } ++ if (Options.check("multiline")) ++ sb.append(" )"); ++ return sb.toString(); ++} ++ ++/** Returns the shared key's algorithm */ ++public Name ++getAlgorithm() { ++ return alg; ++} ++ ++/** Returns the time that this record was generated */ ++public Date ++getTimeSigned() { ++ return timeSigned; ++} ++ ++/** Returns the time fudge factor */ ++public int ++getFudge() { ++ return fudge; ++} ++ ++/** Returns the signature */ ++public byte [] ++getSignature() { ++ return signature; ++} ++ ++/** Returns the original message ID */ ++public int ++getOriginalID() { ++ return originalID; ++} ++ ++/** Returns the extended error */ ++public int ++getError() { ++ return error; ++} ++ ++/** Returns the other data */ ++public byte [] ++getOther() { ++ return other; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ alg.toWire(out, null, canonical); ++ ++ long time = timeSigned.getTime() / 1000; ++ int timeHigh = (int) (time >> 32); ++ long timeLow = (time & 0xFFFFFFFFL); ++ out.writeU16(timeHigh); ++ out.writeU32(timeLow); ++ out.writeU16(fudge); ++ ++ out.writeU16(signature.length); ++ out.writeByteArray(signature); ++ ++ out.writeU16(originalID); ++ out.writeU16(error); ++ ++ if (other != null) { ++ out.writeU16(other.length); ++ out.writeByteArray(other); ++ } ++ else ++ out.writeU16(0); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/TTL.java b/external/asmack/build/src/trunk/org/xbill/DNS/TTL.java +new file mode 100644 +index 0000000..01bf416 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/TTL.java +@@ -0,0 +1,113 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Routines for parsing BIND-style TTL values. These values consist of ++ * numbers followed by 1 letter units of time (W - week, D - day, H - hour, ++ * M - minute, S - second). ++ * ++ * @author Brian Wellington ++ */ ++ ++public final class TTL { ++ ++public static final long MAX_VALUE = 0x7FFFFFFFL; ++ ++private ++TTL() {} ++ ++static void ++check(long i) { ++ if (i < 0 || i > MAX_VALUE) ++ throw new InvalidTTLException(i); ++} ++ ++/** ++ * Parses a TTL-like value, which can either be expressed as a number or a ++ * BIND-style string with numbers and units. ++ * @param s The string representing the numeric value. ++ * @param clamp Whether to clamp values in the range [MAX_VALUE + 1, 2^32 -1] ++ * to MAX_VALUE. This should be donw for TTLs, but not other values which ++ * can be expressed in this format. ++ * @return The value as a number of seconds ++ * @throws NumberFormatException The string was not in a valid TTL format. ++ */ ++public static long ++parse(String s, boolean clamp) { ++ if (s == null || s.length() == 0 || !Character.isDigit(s.charAt(0))) ++ throw new NumberFormatException(); ++ long value = 0; ++ long ttl = 0; ++ for (int i = 0; i < s.length(); i++) { ++ char c = s.charAt(i); ++ long oldvalue = value; ++ if (Character.isDigit(c)) { ++ value = (value * 10) + Character.getNumericValue(c); ++ if (value < oldvalue) ++ throw new NumberFormatException(); ++ } else { ++ switch (Character.toUpperCase(c)) { ++ case 'W': value *= 7; ++ case 'D': value *= 24; ++ case 'H': value *= 60; ++ case 'M': value *= 60; ++ case 'S': break; ++ default: throw new NumberFormatException(); ++ } ++ ttl += value; ++ value = 0; ++ if (ttl > 0xFFFFFFFFL) ++ throw new NumberFormatException(); ++ } ++ } ++ if (ttl == 0) ++ ttl = value; ++ ++ if (ttl > 0xFFFFFFFFL) ++ throw new NumberFormatException(); ++ else if (ttl > MAX_VALUE && clamp) ++ ttl = MAX_VALUE; ++ return ttl; ++} ++ ++/** ++ * Parses a TTL, which can either be expressed as a number or a BIND-style ++ * string with numbers and units. ++ * @param s The string representing the TTL ++ * @return The TTL as a number of seconds ++ * @throws NumberFormatException The string was not in a valid TTL format. ++ */ ++public static long ++parseTTL(String s) { ++ return parse(s, true); ++} ++ ++public static String ++format(long ttl) { ++ TTL.check(ttl); ++ StringBuffer sb = new StringBuffer(); ++ long secs, mins, hours, days, weeks; ++ secs = ttl % 60; ++ ttl /= 60; ++ mins = ttl % 60; ++ ttl /= 60; ++ hours = ttl % 24; ++ ttl /= 24; ++ days = ttl % 7; ++ ttl /= 7; ++ weeks = ttl; ++ if (weeks > 0) ++ sb.append(weeks + "W"); ++ if (days > 0) ++ sb.append(days + "D"); ++ if (hours > 0) ++ sb.append(hours + "H"); ++ if (mins > 0) ++ sb.append(mins + "M"); ++ if (secs > 0 || (weeks == 0 && days == 0 && hours == 0 && mins == 0)) ++ sb.append(secs + "S"); ++ return sb.toString(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/TXTBase.java b/external/asmack/build/src/trunk/org/xbill/DNS/TXTBase.java +new file mode 100644 +index 0000000..fd99bfb +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/TXTBase.java +@@ -0,0 +1,123 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.util.*; ++ ++/** ++ * Implements common functionality for the many record types whose format ++ * is a list of strings. ++ * ++ * @author Brian Wellington ++ */ ++ ++abstract class TXTBase extends Record { ++ ++private static final long serialVersionUID = -4319510507246305931L; ++ ++protected List strings; ++ ++protected ++TXTBase() {} ++ ++protected ++TXTBase(Name name, int type, int dclass, long ttl) { ++ super(name, type, dclass, ttl); ++} ++ ++protected ++TXTBase(Name name, int type, int dclass, long ttl, List strings) { ++ super(name, type, dclass, ttl); ++ if (strings == null) ++ throw new IllegalArgumentException("strings must not be null"); ++ this.strings = new ArrayList(strings.size()); ++ Iterator it = strings.iterator(); ++ try { ++ while (it.hasNext()) { ++ String s = (String) it.next(); ++ this.strings.add(byteArrayFromString(s)); ++ } ++ } ++ catch (TextParseException e) { ++ throw new IllegalArgumentException(e.getMessage()); ++ } ++} ++ ++protected ++TXTBase(Name name, int type, int dclass, long ttl, String string) { ++ this(name, type, dclass, ttl, Collections.singletonList(string)); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ strings = new ArrayList(2); ++ while (in.remaining() > 0) { ++ byte [] b = in.readCountedString(); ++ strings.add(b); ++ } ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ strings = new ArrayList(2); ++ while (true) { ++ Tokenizer.Token t = st.get(); ++ if (!t.isString()) ++ break; ++ try { ++ strings.add(byteArrayFromString(t.value)); ++ } ++ catch (TextParseException e) { ++ throw st.exception(e.getMessage()); ++ } ++ ++ } ++ st.unget(); ++} ++ ++/** converts to a String */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ Iterator it = strings.iterator(); ++ while (it.hasNext()) { ++ byte [] array = (byte []) it.next(); ++ sb.append(byteArrayToString(array, true)); ++ if (it.hasNext()) ++ sb.append(" "); ++ } ++ return sb.toString(); ++} ++ ++/** ++ * Returns the text strings ++ * @return A list of Strings corresponding to the text strings. ++ */ ++public List ++getStrings() { ++ List list = new ArrayList(strings.size()); ++ for (int i = 0; i < strings.size(); i++) ++ list.add(byteArrayToString((byte []) strings.get(i), false)); ++ return list; ++} ++ ++/** ++ * Returns the text strings ++ * @return A list of byte arrays corresponding to the text strings. ++ */ ++public List ++getStringsAsByteArrays() { ++ return strings; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ Iterator it = strings.iterator(); ++ while (it.hasNext()) { ++ byte [] b = (byte []) it.next(); ++ out.writeCountedString(b); ++ } ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/TXTRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/TXTRecord.java +new file mode 100644 +index 0000000..ea5de04 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/TXTRecord.java +@@ -0,0 +1,44 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.util.*; ++ ++/** ++ * Text - stores text strings ++ * ++ * @author Brian Wellington ++ */ ++ ++public class TXTRecord extends TXTBase { ++ ++private static final long serialVersionUID = -5780785764284221342L; ++ ++TXTRecord() {} ++ ++Record ++getObject() { ++ return new TXTRecord(); ++} ++ ++/** ++ * Creates a TXT Record from the given data ++ * @param strings The text strings ++ * @throws IllegalArgumentException One of the strings has invalid escapes ++ */ ++public ++TXTRecord(Name name, int dclass, long ttl, List strings) { ++ super(name, Type.TXT, dclass, ttl, strings); ++} ++ ++/** ++ * Creates a TXT Record from the given data ++ * @param string One text string ++ * @throws IllegalArgumentException The string has invalid escapes ++ */ ++public ++TXTRecord(Name name, int dclass, long ttl, String string) { ++ super(name, Type.TXT, dclass, ttl, string); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/TextParseException.java b/external/asmack/build/src/trunk/org/xbill/DNS/TextParseException.java +new file mode 100644 +index 0000000..3b9a425 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/TextParseException.java +@@ -0,0 +1,25 @@ ++// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++/** ++ * An exception thrown when unable to parse text. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class TextParseException extends IOException { ++ ++public ++TextParseException() { ++ super(); ++} ++ ++public ++TextParseException(String s) { ++ super(s); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Tokenizer.java b/external/asmack/build/src/trunk/org/xbill/DNS/Tokenizer.java +new file mode 100644 +index 0000000..bc637ab +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Tokenizer.java +@@ -0,0 +1,713 @@ ++// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) ++// ++// Copyright (C) 2003-2004 Nominum, Inc. ++// ++// Permission to use, copy, modify, and distribute this software for any ++// purpose with or without fee is hereby granted, provided that the above ++// copyright notice and this permission notice appear in all copies. ++// ++// THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES ++// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR ANY ++// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT ++// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++// ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.net.*; ++ ++import org.xbill.DNS.utils.*; ++ ++/** ++ * Tokenizer is used to parse DNS records and zones from text format, ++ * ++ * @author Brian Wellington ++ * @author Bob Halley ++ */ ++ ++public class Tokenizer { ++ ++private static String delim = " \t\n;()\""; ++private static String quotes = "\""; ++ ++/** End of file */ ++public static final int EOF = 0; ++ ++/** End of line */ ++public static final int EOL = 1; ++ ++/** Whitespace; only returned when wantWhitespace is set */ ++public static final int WHITESPACE = 2; ++ ++/** An identifier (unquoted string) */ ++public static final int IDENTIFIER = 3; ++ ++/** A quoted string */ ++public static final int QUOTED_STRING = 4; ++ ++/** A comment; only returned when wantComment is set */ ++public static final int COMMENT = 5; ++ ++private PushbackInputStream is; ++private boolean ungottenToken; ++private int multiline; ++private boolean quoting; ++private String delimiters; ++private Token current; ++private StringBuffer sb; ++private boolean wantClose; ++ ++private String filename; ++private int line; ++ ++public static class Token { ++ /** The type of token. */ ++ public int type; ++ ++ /** The value of the token, or null for tokens without values. */ ++ public String value; ++ ++ private ++ Token() { ++ type = -1; ++ value = null; ++ } ++ ++ private Token ++ set(int type, StringBuffer value) { ++ if (type < 0) ++ throw new IllegalArgumentException(); ++ this.type = type; ++ this.value = value == null ? null : value.toString(); ++ return this; ++ } ++ ++ /** ++ * Converts the token to a string containing a representation useful ++ * for debugging. ++ */ ++ public String ++ toString() { ++ switch (type) { ++ case EOF: ++ return ""; ++ case EOL: ++ return ""; ++ case WHITESPACE: ++ return ""; ++ case IDENTIFIER: ++ return ""; ++ case QUOTED_STRING: ++ return ""; ++ case COMMENT: ++ return ""; ++ default: ++ return ""; ++ } ++ } ++ ++ /** Indicates whether this token contains a string. */ ++ public boolean ++ isString() { ++ return (type == IDENTIFIER || type == QUOTED_STRING); ++ } ++ ++ /** Indicates whether this token contains an EOL or EOF. */ ++ public boolean ++ isEOL() { ++ return (type == EOL || type == EOF); ++ } ++} ++ ++static class TokenizerException extends TextParseException { ++ String message; ++ ++ public ++ TokenizerException(String filename, int line, String message) { ++ super(filename + ":" + line + ": " + message); ++ this.message = message; ++ } ++ ++ public String ++ getBaseMessage() { ++ return message; ++ } ++} ++ ++/** ++ * Creates a Tokenizer from an arbitrary input stream. ++ * @param is The InputStream to tokenize. ++ */ ++public ++Tokenizer(InputStream is) { ++ if (!(is instanceof BufferedInputStream)) ++ is = new BufferedInputStream(is); ++ this.is = new PushbackInputStream(is, 2); ++ ungottenToken = false; ++ multiline = 0; ++ quoting = false; ++ delimiters = delim; ++ current = new Token(); ++ sb = new StringBuffer(); ++ filename = ""; ++ line = 1; ++} ++ ++/** ++ * Creates a Tokenizer from a string. ++ * @param s The String to tokenize. ++ */ ++public ++Tokenizer(String s) { ++ this(new ByteArrayInputStream(s.getBytes())); ++} ++ ++/** ++ * Creates a Tokenizer from a file. ++ * @param f The File to tokenize. ++ */ ++public ++Tokenizer(File f) throws FileNotFoundException { ++ this(new FileInputStream(f)); ++ wantClose = true; ++ filename = f.getName(); ++} ++ ++private int ++getChar() throws IOException { ++ int c = is.read(); ++ if (c == '\r') { ++ int next = is.read(); ++ if (next != '\n') ++ is.unread(next); ++ c = '\n'; ++ } ++ if (c == '\n') ++ line++; ++ return c; ++} ++ ++private void ++ungetChar(int c) throws IOException { ++ if (c == -1) ++ return; ++ is.unread(c); ++ if (c == '\n') ++ line--; ++} ++ ++private int ++skipWhitespace() throws IOException { ++ int skipped = 0; ++ while (true) { ++ int c = getChar(); ++ if (c != ' ' && c != '\t') { ++ if (!(c == '\n' && multiline > 0)) { ++ ungetChar(c); ++ return skipped; ++ } ++ } ++ skipped++; ++ } ++} ++ ++private void ++checkUnbalancedParens() throws TextParseException { ++ if (multiline > 0) ++ throw exception("unbalanced parentheses"); ++} ++ ++/** ++ * Gets the next token from a tokenizer. ++ * @param wantWhitespace If true, leading whitespace will be returned as a ++ * token. ++ * @param wantComment If true, comments are returned as tokens. ++ * @return The next token in the stream. ++ * @throws TextParseException The input was invalid. ++ * @throws IOException An I/O error occurred. ++ */ ++public Token ++get(boolean wantWhitespace, boolean wantComment) throws IOException { ++ int type; ++ int c; ++ ++ if (ungottenToken) { ++ ungottenToken = false; ++ if (current.type == WHITESPACE) { ++ if (wantWhitespace) ++ return current; ++ } else if (current.type == COMMENT) { ++ if (wantComment) ++ return current; ++ } else { ++ if (current.type == EOL) ++ line++; ++ return current; ++ } ++ } ++ int skipped = skipWhitespace(); ++ if (skipped > 0 && wantWhitespace) ++ return current.set(WHITESPACE, null); ++ type = IDENTIFIER; ++ sb.setLength(0); ++ while (true) { ++ c = getChar(); ++ if (c == -1 || delimiters.indexOf(c) != -1) { ++ if (c == -1) { ++ if (quoting) ++ throw exception("EOF in " + ++ "quoted string"); ++ else if (sb.length() == 0) ++ return current.set(EOF, null); ++ else ++ return current.set(type, sb); ++ } ++ if (sb.length() == 0 && type != QUOTED_STRING) { ++ if (c == '(') { ++ multiline++; ++ skipWhitespace(); ++ continue; ++ } else if (c == ')') { ++ if (multiline <= 0) ++ throw exception("invalid " + ++ "close " + ++ "parenthesis"); ++ multiline--; ++ skipWhitespace(); ++ continue; ++ } else if (c == '"') { ++ if (!quoting) { ++ quoting = true; ++ delimiters = quotes; ++ type = QUOTED_STRING; ++ } else { ++ quoting = false; ++ delimiters = delim; ++ skipWhitespace(); ++ } ++ continue; ++ } else if (c == '\n') { ++ return current.set(EOL, null); ++ } else if (c == ';') { ++ while (true) { ++ c = getChar(); ++ if (c == '\n' || c == -1) ++ break; ++ sb.append((char)c); ++ } ++ if (wantComment) { ++ ungetChar(c); ++ return current.set(COMMENT, sb); ++ } else if (c == -1 && ++ type != QUOTED_STRING) ++ { ++ checkUnbalancedParens(); ++ return current.set(EOF, null); ++ } else if (multiline > 0) { ++ skipWhitespace(); ++ sb.setLength(0); ++ continue; ++ } else ++ return current.set(EOL, null); ++ } else ++ throw new IllegalStateException(); ++ } else ++ ungetChar(c); ++ break; ++ } else if (c == '\\') { ++ c = getChar(); ++ if (c == -1) ++ throw exception("unterminated escape sequence"); ++ sb.append('\\'); ++ } else if (quoting && c == '\n') { ++ throw exception("newline in quoted string"); ++ } ++ sb.append((char)c); ++ } ++ if (sb.length() == 0 && type != QUOTED_STRING) { ++ checkUnbalancedParens(); ++ return current.set(EOF, null); ++ } ++ return current.set(type, sb); ++} ++ ++/** ++ * Gets the next token from a tokenizer, ignoring whitespace and comments. ++ * @return The next token in the stream. ++ * @throws TextParseException The input was invalid. ++ * @throws IOException An I/O error occurred. ++ */ ++public Token ++get() throws IOException { ++ return get(false, false); ++} ++ ++/** ++ * Returns a token to the stream, so that it will be returned by the next call ++ * to get(). ++ * @throws IllegalStateException There are already ungotten tokens. ++ */ ++public void ++unget() { ++ if (ungottenToken) ++ throw new IllegalStateException ++ ("Cannot unget multiple tokens"); ++ if (current.type == EOL) ++ line--; ++ ungottenToken = true; ++} ++ ++/** ++ * Gets the next token from a tokenizer and converts it to a string. ++ * @return The next token in the stream, as a string. ++ * @throws TextParseException The input was invalid or not a string. ++ * @throws IOException An I/O error occurred. ++ */ ++public String ++getString() throws IOException { ++ Token next = get(); ++ if (!next.isString()) { ++ throw exception("expected a string"); ++ } ++ return next.value; ++} ++ ++private String ++_getIdentifier(String expected) throws IOException { ++ Token next = get(); ++ if (next.type != IDENTIFIER) ++ throw exception("expected " + expected); ++ return next.value; ++} ++ ++/** ++ * Gets the next token from a tokenizer, ensures it is an unquoted string, ++ * and converts it to a string. ++ * @return The next token in the stream, as a string. ++ * @throws TextParseException The input was invalid or not an unquoted string. ++ * @throws IOException An I/O error occurred. ++ */ ++public String ++getIdentifier() throws IOException { ++ return _getIdentifier("an identifier"); ++} ++ ++/** ++ * Gets the next token from a tokenizer and converts it to a long. ++ * @return The next token in the stream, as a long. ++ * @throws TextParseException The input was invalid or not a long. ++ * @throws IOException An I/O error occurred. ++ */ ++public long ++getLong() throws IOException { ++ String next = _getIdentifier("an integer"); ++ if (!Character.isDigit(next.charAt(0))) ++ throw exception("expected an integer"); ++ try { ++ return Long.parseLong(next); ++ } catch (NumberFormatException e) { ++ throw exception("expected an integer"); ++ } ++} ++ ++/** ++ * Gets the next token from a tokenizer and converts it to an unsigned 32 bit ++ * integer. ++ * @return The next token in the stream, as an unsigned 32 bit integer. ++ * @throws TextParseException The input was invalid or not an unsigned 32 ++ * bit integer. ++ * @throws IOException An I/O error occurred. ++ */ ++public long ++getUInt32() throws IOException { ++ long l = getLong(); ++ if (l < 0 || l > 0xFFFFFFFFL) ++ throw exception("expected an 32 bit unsigned integer"); ++ return l; ++} ++ ++/** ++ * Gets the next token from a tokenizer and converts it to an unsigned 16 bit ++ * integer. ++ * @return The next token in the stream, as an unsigned 16 bit integer. ++ * @throws TextParseException The input was invalid or not an unsigned 16 ++ * bit integer. ++ * @throws IOException An I/O error occurred. ++ */ ++public int ++getUInt16() throws IOException { ++ long l = getLong(); ++ if (l < 0 || l > 0xFFFFL) ++ throw exception("expected an 16 bit unsigned integer"); ++ return (int) l; ++} ++ ++/** ++ * Gets the next token from a tokenizer and converts it to an unsigned 8 bit ++ * integer. ++ * @return The next token in the stream, as an unsigned 8 bit integer. ++ * @throws TextParseException The input was invalid or not an unsigned 8 ++ * bit integer. ++ * @throws IOException An I/O error occurred. ++ */ ++public int ++getUInt8() throws IOException { ++ long l = getLong(); ++ if (l < 0 || l > 0xFFL) ++ throw exception("expected an 8 bit unsigned integer"); ++ return (int) l; ++} ++ ++/** ++ * Gets the next token from a tokenizer and parses it as a TTL. ++ * @return The next token in the stream, as an unsigned 32 bit integer. ++ * @throws TextParseException The input was not valid. ++ * @throws IOException An I/O error occurred. ++ * @see TTL ++ */ ++public long ++getTTL() throws IOException { ++ String next = _getIdentifier("a TTL value"); ++ try { ++ return TTL.parseTTL(next); ++ } ++ catch (NumberFormatException e) { ++ throw exception("expected a TTL value"); ++ } ++} ++ ++/** ++ * Gets the next token from a tokenizer and parses it as if it were a TTL. ++ * @return The next token in the stream, as an unsigned 32 bit integer. ++ * @throws TextParseException The input was not valid. ++ * @throws IOException An I/O error occurred. ++ * @see TTL ++ */ ++public long ++getTTLLike() throws IOException { ++ String next = _getIdentifier("a TTL-like value"); ++ try { ++ return TTL.parse(next, false); ++ } ++ catch (NumberFormatException e) { ++ throw exception("expected a TTL-like value"); ++ } ++} ++ ++/** ++ * Gets the next token from a tokenizer and converts it to a name. ++ * @param origin The origin to append to relative names. ++ * @return The next token in the stream, as a name. ++ * @throws TextParseException The input was invalid or not a valid name. ++ * @throws IOException An I/O error occurred. ++ * @throws RelativeNameException The parsed name was relative, even with the ++ * origin. ++ * @see Name ++ */ ++public Name ++getName(Name origin) throws IOException { ++ String next = _getIdentifier("a name"); ++ try { ++ Name name = Name.fromString(next, origin); ++ if (!name.isAbsolute()) ++ throw new RelativeNameException(name); ++ return name; ++ } ++ catch (TextParseException e) { ++ throw exception(e.getMessage()); ++ } ++} ++ ++/** ++ * Gets the next token from a tokenizer and converts it to an IP Address. ++ * @param family The address family. ++ * @return The next token in the stream, as an InetAddress ++ * @throws TextParseException The input was invalid or not a valid address. ++ * @throws IOException An I/O error occurred. ++ * @see Address ++ */ ++public InetAddress ++getAddress(int family) throws IOException { ++ String next = _getIdentifier("an address"); ++ try { ++ return Address.getByAddress(next, family); ++ } ++ catch (UnknownHostException e) { ++ throw exception(e.getMessage()); ++ } ++} ++ ++/** ++ * Gets the next token from a tokenizer, which must be an EOL or EOF. ++ * @throws TextParseException The input was invalid or not an EOL or EOF token. ++ * @throws IOException An I/O error occurred. ++ */ ++public void ++getEOL() throws IOException { ++ Token next = get(); ++ if (next.type != EOL && next.type != EOF) { ++ throw exception("expected EOL or EOF"); ++ } ++} ++ ++/** ++ * Returns a concatenation of the remaining strings from a Tokenizer. ++ */ ++private String ++remainingStrings() throws IOException { ++ StringBuffer buffer = null; ++ while (true) { ++ Tokenizer.Token t = get(); ++ if (!t.isString()) ++ break; ++ if (buffer == null) ++ buffer = new StringBuffer(); ++ buffer.append(t.value); ++ } ++ unget(); ++ if (buffer == null) ++ return null; ++ return buffer.toString(); ++} ++ ++/** ++ * Gets the remaining string tokens until an EOL/EOF is seen, concatenates ++ * them together, and converts the base64 encoded data to a byte array. ++ * @param required If true, an exception will be thrown if no strings remain; ++ * otherwise null be be returned. ++ * @return The byte array containing the decoded strings, or null if there ++ * were no strings to decode. ++ * @throws TextParseException The input was invalid. ++ * @throws IOException An I/O error occurred. ++ */ ++public byte [] ++getBase64(boolean required) throws IOException { ++ String s = remainingStrings(); ++ if (s == null) { ++ if (required) ++ throw exception("expected base64 encoded string"); ++ else ++ return null; ++ } ++ byte [] array = base64.fromString(s); ++ if (array == null) ++ throw exception("invalid base64 encoding"); ++ return array; ++} ++ ++/** ++ * Gets the remaining string tokens until an EOL/EOF is seen, concatenates ++ * them together, and converts the base64 encoded data to a byte array. ++ * @return The byte array containing the decoded strings, or null if there ++ * were no strings to decode. ++ * @throws TextParseException The input was invalid. ++ * @throws IOException An I/O error occurred. ++ */ ++public byte [] ++getBase64() throws IOException { ++ return getBase64(false); ++} ++ ++/** ++ * Gets the remaining string tokens until an EOL/EOF is seen, concatenates ++ * them together, and converts the hex encoded data to a byte array. ++ * @param required If true, an exception will be thrown if no strings remain; ++ * otherwise null be be returned. ++ * @return The byte array containing the decoded strings, or null if there ++ * were no strings to decode. ++ * @throws TextParseException The input was invalid. ++ * @throws IOException An I/O error occurred. ++ */ ++public byte [] ++getHex(boolean required) throws IOException { ++ String s = remainingStrings(); ++ if (s == null) { ++ if (required) ++ throw exception("expected hex encoded string"); ++ else ++ return null; ++ } ++ byte [] array = base16.fromString(s); ++ if (array == null) ++ throw exception("invalid hex encoding"); ++ return array; ++} ++ ++/** ++ * Gets the remaining string tokens until an EOL/EOF is seen, concatenates ++ * them together, and converts the hex encoded data to a byte array. ++ * @return The byte array containing the decoded strings, or null if there ++ * were no strings to decode. ++ * @throws TextParseException The input was invalid. ++ * @throws IOException An I/O error occurred. ++ */ ++public byte [] ++getHex() throws IOException { ++ return getHex(false); ++} ++ ++/** ++ * Gets the next token from a tokenizer and decodes it as hex. ++ * @return The byte array containing the decoded string. ++ * @throws TextParseException The input was invalid. ++ * @throws IOException An I/O error occurred. ++ */ ++public byte [] ++getHexString() throws IOException { ++ String next = _getIdentifier("a hex string"); ++ byte [] array = base16.fromString(next); ++ if (array == null) ++ throw exception("invalid hex encoding"); ++ return array; ++} ++ ++/** ++ * Gets the next token from a tokenizer and decodes it as base32. ++ * @param b32 The base32 context to decode with. ++ * @return The byte array containing the decoded string. ++ * @throws TextParseException The input was invalid. ++ * @throws IOException An I/O error occurred. ++ */ ++public byte [] ++getBase32String(base32 b32) throws IOException { ++ String next = _getIdentifier("a base32 string"); ++ byte [] array = b32.fromString(next); ++ if (array == null) ++ throw exception("invalid base32 encoding"); ++ return array; ++} ++ ++/** ++ * Creates an exception which includes the current state in the error message ++ * @param s The error message to include. ++ * @return The exception to be thrown ++ */ ++public TextParseException ++exception(String s) { ++ return new TokenizerException(filename, line, s); ++} ++ ++/** ++ * Closes any files opened by this tokenizer. ++ */ ++public void ++close() { ++ if (wantClose) { ++ try { ++ is.close(); ++ } ++ catch (IOException e) { ++ } ++ } ++} ++ ++protected void ++finalize() { ++ close(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Type.java b/external/asmack/build/src/trunk/org/xbill/DNS/Type.java +new file mode 100644 +index 0000000..a5d479a +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Type.java +@@ -0,0 +1,356 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.util.HashMap; ++ ++/** ++ * Constants and functions relating to DNS Types ++ * ++ * @author Brian Wellington ++ */ ++ ++public final class Type { ++ ++/** Address */ ++public static final int A = 1; ++ ++/** Name server */ ++public static final int NS = 2; ++ ++/** Mail destination */ ++public static final int MD = 3; ++ ++/** Mail forwarder */ ++public static final int MF = 4; ++ ++/** Canonical name (alias) */ ++public static final int CNAME = 5; ++ ++/** Start of authority */ ++public static final int SOA = 6; ++ ++/** Mailbox domain name */ ++public static final int MB = 7; ++ ++/** Mail group member */ ++public static final int MG = 8; ++ ++/** Mail rename name */ ++public static final int MR = 9; ++ ++/** Null record */ ++public static final int NULL = 10; ++ ++/** Well known services */ ++public static final int WKS = 11; ++ ++/** Domain name pointer */ ++public static final int PTR = 12; ++ ++/** Host information */ ++public static final int HINFO = 13; ++ ++/** Mailbox information */ ++public static final int MINFO = 14; ++ ++/** Mail routing information */ ++public static final int MX = 15; ++ ++/** Text strings */ ++public static final int TXT = 16; ++ ++/** Responsible person */ ++public static final int RP = 17; ++ ++/** AFS cell database */ ++public static final int AFSDB = 18; ++ ++/** X.25 calling address */ ++public static final int X25 = 19; ++ ++/** ISDN calling address */ ++public static final int ISDN = 20; ++ ++/** Router */ ++public static final int RT = 21; ++ ++/** NSAP address */ ++public static final int NSAP = 22; ++ ++/** Reverse NSAP address (deprecated) */ ++public static final int NSAP_PTR = 23; ++ ++/** Signature */ ++public static final int SIG = 24; ++ ++/** Key */ ++public static final int KEY = 25; ++ ++/** X.400 mail mapping */ ++public static final int PX = 26; ++ ++/** Geographical position (withdrawn) */ ++public static final int GPOS = 27; ++ ++/** IPv6 address */ ++public static final int AAAA = 28; ++ ++/** Location */ ++public static final int LOC = 29; ++ ++/** Next valid name in zone */ ++public static final int NXT = 30; ++ ++/** Endpoint identifier */ ++public static final int EID = 31; ++ ++/** Nimrod locator */ ++public static final int NIMLOC = 32; ++ ++/** Server selection */ ++public static final int SRV = 33; ++ ++/** ATM address */ ++public static final int ATMA = 34; ++ ++/** Naming authority pointer */ ++public static final int NAPTR = 35; ++ ++/** Key exchange */ ++public static final int KX = 36; ++ ++/** Certificate */ ++public static final int CERT = 37; ++ ++/** IPv6 address (experimental) */ ++public static final int A6 = 38; ++ ++/** Non-terminal name redirection */ ++public static final int DNAME = 39; ++ ++/** Options - contains EDNS metadata */ ++public static final int OPT = 41; ++ ++/** Address Prefix List */ ++public static final int APL = 42; ++ ++/** Delegation Signer */ ++public static final int DS = 43; ++ ++/** SSH Key Fingerprint */ ++public static final int SSHFP = 44; ++ ++/** IPSEC key */ ++public static final int IPSECKEY = 45; ++ ++/** Resource Record Signature */ ++public static final int RRSIG = 46; ++ ++/** Next Secure Name */ ++public static final int NSEC = 47; ++ ++/** DNSSEC Key */ ++public static final int DNSKEY = 48; ++ ++/** Dynamic Host Configuration Protocol (DHCP) ID */ ++public static final int DHCID = 49; ++ ++/** Next SECure, 3rd edition, RFC 5155 */ ++public static final int NSEC3 = 50; ++ ++public static final int NSEC3PARAM = 51; ++ ++/** Sender Policy Framework (experimental) */ ++public static final int SPF = 99; ++ ++/** Transaction key - used to compute a shared secret or exchange a key */ ++public static final int TKEY = 249; ++ ++/** Transaction signature */ ++public static final int TSIG = 250; ++ ++/** Incremental zone transfer */ ++public static final int IXFR = 251; ++ ++/** Zone transfer */ ++public static final int AXFR = 252; ++ ++/** Transfer mailbox records */ ++public static final int MAILB = 253; ++ ++/** Transfer mail agent records */ ++public static final int MAILA = 254; ++ ++/** Matches any type */ ++public static final int ANY = 255; ++ ++/** DNSSEC Lookaside Validation, RFC 4431 . */ ++public static final int DLV = 32769; ++ ++ ++private static class TypeMnemonic extends Mnemonic { ++ private HashMap objects; ++ ++ public ++ TypeMnemonic() { ++ super("Type", CASE_UPPER); ++ setPrefix("TYPE"); ++ objects = new HashMap(); ++ } ++ ++ public void ++ add(int val, String str, Record proto) { ++ super.add(val, str); ++ objects.put(Mnemonic.toInteger(val), proto); ++ } ++ ++ public void ++ check(int val) { ++ Type.check(val); ++ } ++ ++ public Record ++ getProto(int val) { ++ check(val); ++ return (Record) objects.get(toInteger(val)); ++ } ++} ++ ++private static TypeMnemonic types = new TypeMnemonic(); ++ ++static { ++ types.add(A, "A", new ARecord()); ++ types.add(NS, "NS", new NSRecord()); ++ types.add(MD, "MD", new MDRecord()); ++ types.add(MF, "MF", new MFRecord()); ++ types.add(CNAME, "CNAME", new CNAMERecord()); ++ types.add(SOA, "SOA", new SOARecord()); ++ types.add(MB, "MB", new MBRecord()); ++ types.add(MG, "MG", new MGRecord()); ++ types.add(MR, "MR", new MRRecord()); ++ types.add(NULL, "NULL", new NULLRecord()); ++ types.add(WKS, "WKS", new WKSRecord()); ++ types.add(PTR, "PTR", new PTRRecord()); ++ types.add(HINFO, "HINFO", new HINFORecord()); ++ types.add(MINFO, "MINFO", new MINFORecord()); ++ types.add(MX, "MX", new MXRecord()); ++ types.add(TXT, "TXT", new TXTRecord()); ++ types.add(RP, "RP", new RPRecord()); ++ types.add(AFSDB, "AFSDB", new AFSDBRecord()); ++ types.add(X25, "X25", new X25Record()); ++ types.add(ISDN, "ISDN", new ISDNRecord()); ++ types.add(RT, "RT", new RTRecord()); ++ types.add(NSAP, "NSAP", new NSAPRecord()); ++ types.add(NSAP_PTR, "NSAP-PTR", new NSAP_PTRRecord()); ++ types.add(SIG, "SIG", new SIGRecord()); ++ types.add(KEY, "KEY", new KEYRecord()); ++ types.add(PX, "PX", new PXRecord()); ++ types.add(GPOS, "GPOS", new GPOSRecord()); ++ types.add(AAAA, "AAAA", new AAAARecord()); ++ types.add(LOC, "LOC", new LOCRecord()); ++ types.add(NXT, "NXT", new NXTRecord()); ++ types.add(EID, "EID"); ++ types.add(NIMLOC, "NIMLOC"); ++ types.add(SRV, "SRV", new SRVRecord()); ++ types.add(ATMA, "ATMA"); ++ types.add(NAPTR, "NAPTR", new NAPTRRecord()); ++ types.add(KX, "KX", new KXRecord()); ++ types.add(CERT, "CERT", new CERTRecord()); ++ types.add(A6, "A6", new A6Record()); ++ types.add(DNAME, "DNAME", new DNAMERecord()); ++ types.add(OPT, "OPT", new OPTRecord()); ++ types.add(APL, "APL", new APLRecord()); ++ types.add(DS, "DS", new DSRecord()); ++ types.add(SSHFP, "SSHFP", new SSHFPRecord()); ++ types.add(IPSECKEY, "IPSECKEY", new IPSECKEYRecord()); ++ types.add(RRSIG, "RRSIG", new RRSIGRecord()); ++ types.add(NSEC, "NSEC", new NSECRecord()); ++ types.add(DNSKEY, "DNSKEY", new DNSKEYRecord()); ++ types.add(DHCID, "DHCID", new DHCIDRecord()); ++ types.add(NSEC3, "NSEC3", new NSEC3Record()); ++ types.add(NSEC3PARAM, "NSEC3PARAM", new NSEC3PARAMRecord()); ++ types.add(SPF, "SPF", new SPFRecord()); ++ types.add(TKEY, "TKEY", new TKEYRecord()); ++ types.add(TSIG, "TSIG", new TSIGRecord()); ++ types.add(IXFR, "IXFR"); ++ types.add(AXFR, "AXFR"); ++ types.add(MAILB, "MAILB"); ++ types.add(MAILA, "MAILA"); ++ types.add(ANY, "ANY"); ++ types.add(DLV, "DLV", new DLVRecord()); ++} ++ ++private ++Type() { ++} ++ ++/** ++ * Checks that a numeric Type is valid. ++ * @throws InvalidTypeException The type is out of range. ++ */ ++public static void ++check(int val) { ++ if (val < 0 || val > 0xFFFF) ++ throw new InvalidTypeException(val); ++} ++ ++/** ++ * Converts a numeric Type into a String ++ * @param val The type value. ++ * @return The canonical string representation of the type ++ * @throws InvalidTypeException The type is out of range. ++ */ ++public static String ++string(int val) { ++ return types.getText(val); ++} ++ ++/** ++ * Converts a String representation of an Type into its numeric value. ++ * @param s The string representation of the type ++ * @param numberok Whether a number will be accepted or not. ++ * @return The type code, or -1 on error. ++ */ ++public static int ++value(String s, boolean numberok) { ++ int val = types.getValue(s); ++ if (val == -1 && numberok) { ++ val = types.getValue("TYPE" + s); ++ } ++ return val; ++} ++ ++/** ++ * Converts a String representation of an Type into its numeric value ++ * @return The type code, or -1 on error. ++ */ ++public static int ++value(String s) { ++ return value(s, false); ++} ++ ++static Record ++getProto(int val) { ++ return types.getProto(val); ++} ++ ++/** Is this type valid for a record (a non-meta type)? */ ++public static boolean ++isRR(int type) { ++ switch (type) { ++ case OPT: ++ case TKEY: ++ case TSIG: ++ case IXFR: ++ case AXFR: ++ case MAILB: ++ case MAILA: ++ case ANY: ++ return false; ++ default: ++ return true; ++ } ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/TypeBitmap.java b/external/asmack/build/src/trunk/org/xbill/DNS/TypeBitmap.java +new file mode 100644 +index 0000000..628cc35 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/TypeBitmap.java +@@ -0,0 +1,147 @@ ++// Copyright (c) 2004-2009 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * Routines for deal with the lists of types found in NSEC/NSEC3 records. ++ * ++ * @author Brian Wellington ++ */ ++ ++import java.io.*; ++import java.util.*; ++ ++final class TypeBitmap implements Serializable { ++ ++private static final long serialVersionUID = -125354057735389003L; ++ ++private TreeSet types; ++ ++private ++TypeBitmap() { ++ types = new TreeSet(); ++} ++ ++public ++TypeBitmap(int [] array) { ++ this(); ++ for (int i = 0; i < array.length; i++) { ++ Type.check(array[i]); ++ types.add(new Integer(array[i])); ++ } ++} ++ ++public ++TypeBitmap(DNSInput in) throws WireParseException { ++ this(); ++ int lastbase = -1; ++ while (in.remaining() > 0) { ++ if (in.remaining() < 2) ++ throw new WireParseException ++ ("invalid bitmap descriptor"); ++ int mapbase = in.readU8(); ++ if (mapbase < lastbase) ++ throw new WireParseException("invalid ordering"); ++ int maplength = in.readU8(); ++ if (maplength > in.remaining()) ++ throw new WireParseException("invalid bitmap"); ++ for (int i = 0; i < maplength; i++) { ++ int current = in.readU8(); ++ if (current == 0) ++ continue; ++ for (int j = 0; j < 8; j++) { ++ if ((current & (1 << (7 - j))) == 0) ++ continue; ++ int typecode = mapbase * 256 + + i * 8 + j; ++ types.add(Mnemonic.toInteger(typecode)); ++ } ++ } ++ } ++} ++ ++public ++TypeBitmap(Tokenizer st) throws IOException { ++ this(); ++ while (true) { ++ Tokenizer.Token t = st.get(); ++ if (!t.isString()) ++ break; ++ int typecode = Type.value(t.value); ++ if (typecode < 0) { ++ throw st.exception("Invalid type: " + t.value); ++ } ++ types.add(Mnemonic.toInteger(typecode)); ++ } ++ st.unget(); ++} ++ ++public int [] ++toArray() { ++ int [] array = new int[types.size()]; ++ int n = 0; ++ for (Iterator it = types.iterator(); it.hasNext(); ) ++ array[n++] = ((Integer)it.next()).intValue(); ++ return array; ++} ++ ++public String ++toString() { ++ StringBuffer sb = new StringBuffer(); ++ for (Iterator it = types.iterator(); it.hasNext(); ) { ++ int t = ((Integer)it.next()).intValue(); ++ sb.append(Type.string(t)); ++ if (it.hasNext()) ++ sb.append(' '); ++ } ++ return sb.toString(); ++} ++ ++private static void ++mapToWire(DNSOutput out, TreeSet map, int mapbase) { ++ int arraymax = (((Integer)map.last()).intValue()) & 0xFF; ++ int arraylength = (arraymax / 8) + 1; ++ int [] array = new int[arraylength]; ++ out.writeU8(mapbase); ++ out.writeU8(arraylength); ++ for (Iterator it = map.iterator(); it.hasNext(); ) { ++ int typecode = ((Integer)it.next()).intValue(); ++ array[(typecode & 0xFF) / 8] |= (1 << ( 7 - typecode % 8)); ++ } ++ for (int j = 0; j < arraylength; j++) ++ out.writeU8(array[j]); ++} ++ ++public void ++toWire(DNSOutput out) { ++ if (types.size() == 0) ++ return; ++ ++ int mapbase = -1; ++ TreeSet map = new TreeSet(); ++ ++ for (Iterator it = types.iterator(); it.hasNext(); ) { ++ int t = ((Integer)it.next()).intValue(); ++ int base = t >> 8; ++ if (base != mapbase) { ++ if (map.size() > 0) { ++ mapToWire(out, map, mapbase); ++ map.clear(); ++ } ++ mapbase = base; ++ } ++ map.add(new Integer(t)); ++ } ++ mapToWire(out, map, mapbase); ++} ++ ++public boolean ++empty() { ++ return types.isEmpty(); ++} ++ ++public boolean ++contains(int typecode) { ++ return types.contains(Mnemonic.toInteger(typecode)); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/U16NameBase.java b/external/asmack/build/src/trunk/org/xbill/DNS/U16NameBase.java +new file mode 100644 +index 0000000..df3c836 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/U16NameBase.java +@@ -0,0 +1,75 @@ ++// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++/** ++ * Implements common functionality for the many record types whose format ++ * is an unsigned 16 bit integer followed by a name. ++ * ++ * @author Brian Wellington ++ */ ++ ++abstract class U16NameBase extends Record { ++ ++private static final long serialVersionUID = -8315884183112502995L; ++ ++protected int u16Field; ++protected Name nameField; ++ ++protected ++U16NameBase() {} ++ ++protected ++U16NameBase(Name name, int type, int dclass, long ttl) { ++ super(name, type, dclass, ttl); ++} ++ ++protected ++U16NameBase(Name name, int type, int dclass, long ttl, int u16Field, ++ String u16Description, Name nameField, String nameDescription) ++{ ++ super(name, type, dclass, ttl); ++ this.u16Field = checkU16(u16Description, u16Field); ++ this.nameField = checkName(nameDescription, nameField); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ u16Field = in.readU16(); ++ nameField = new Name(in); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ u16Field = st.getUInt16(); ++ nameField = st.getName(origin); ++} ++ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(u16Field); ++ sb.append(" "); ++ sb.append(nameField); ++ return sb.toString(); ++} ++ ++protected int ++getU16Field() { ++ return u16Field; ++} ++ ++protected Name ++getNameField() { ++ return nameField; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeU16(u16Field); ++ nameField.toWire(out, null, canonical); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/UDPClient.java b/external/asmack/build/src/trunk/org/xbill/DNS/UDPClient.java +new file mode 100644 +index 0000000..e752ce4 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/UDPClient.java +@@ -0,0 +1,164 @@ ++// Copyright (c) 2005 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.net.*; ++import java.security.SecureRandom; ++import java.nio.*; ++import java.nio.channels.*; ++ ++final class UDPClient extends Client { ++ ++private static final int EPHEMERAL_START = 1024; ++private static final int EPHEMERAL_STOP = 65535; ++private static final int EPHEMERAL_RANGE = EPHEMERAL_STOP - EPHEMERAL_START; ++ ++private static SecureRandom prng = new SecureRandom(); ++private static volatile boolean prng_initializing = true; ++ ++/* ++ * On some platforms (Windows), the SecureRandom module initialization involves ++ * a call to InetAddress.getLocalHost(), which can end up here if using a ++ * dnsjava name service provider. ++ * ++ * This can cause problems in multiple ways. ++ * - If the SecureRandom seed generation process calls into here, and this ++ * module attempts to seed the local SecureRandom object, the thread hangs. ++ * - If something else calls InetAddress.getLocalHost(), and that causes this ++ * module to seed the local SecureRandom object, the thread hangs. ++ * ++ * To avoid both of these, check at initialization time to see if InetAddress ++ * is in the call chain. If so, initialize the SecureRandom object in a new ++ * thread, and disable port randomization until it completes. ++ */ ++static { ++ new Thread(new Runnable() { ++ public void run() { ++ int n = prng.nextInt(); ++ prng_initializing = false; ++ }}).start(); ++} ++ ++private boolean bound = false; ++ ++public ++UDPClient(long endTime) throws IOException { ++ super(DatagramChannel.open(), endTime); ++} ++ ++private void ++bind_random(InetSocketAddress addr) throws IOException ++{ ++ if (prng_initializing) { ++ try { ++ Thread.sleep(2); ++ } ++ catch (InterruptedException e) { ++ } ++ if (prng_initializing) ++ return; ++ } ++ ++ DatagramChannel channel = (DatagramChannel) key.channel(); ++ InetSocketAddress temp; ++ ++ for (int i = 0; i < 1024; i++) { ++ try { ++ int port = prng.nextInt(EPHEMERAL_RANGE) + ++ EPHEMERAL_START; ++ if (addr != null) ++ temp = new InetSocketAddress(addr.getAddress(), ++ port); ++ else ++ temp = new InetSocketAddress(port); ++ channel.socket().bind(temp); ++ bound = true; ++ return; ++ } ++ catch (SocketException e) { ++ } ++ } ++} ++ ++void ++bind(SocketAddress addr) throws IOException { ++ if (addr == null || ++ (addr instanceof InetSocketAddress && ++ ((InetSocketAddress)addr).getPort() == 0)) ++ { ++ bind_random((InetSocketAddress) addr); ++ if (bound) ++ return; ++ } ++ ++ if (addr != null) { ++ DatagramChannel channel = (DatagramChannel) key.channel(); ++ channel.socket().bind(addr); ++ bound = true; ++ } ++} ++ ++void ++connect(SocketAddress addr) throws IOException { ++ if (!bound) ++ bind(null); ++ DatagramChannel channel = (DatagramChannel) key.channel(); ++ channel.connect(addr); ++} ++ ++void ++send(byte [] data) throws IOException { ++ DatagramChannel channel = (DatagramChannel) key.channel(); ++ verboseLog("UDP write", data); ++ channel.write(ByteBuffer.wrap(data)); ++} ++ ++byte [] ++recv(int max) throws IOException { ++ DatagramChannel channel = (DatagramChannel) key.channel(); ++ byte [] temp = new byte[max]; ++ key.interestOps(SelectionKey.OP_READ); ++ try { ++ while (!key.isReadable()) ++ blockUntil(key, endTime); ++ } ++ finally { ++ if (key.isValid()) ++ key.interestOps(0); ++ } ++ long ret = channel.read(ByteBuffer.wrap(temp)); ++ if (ret <= 0) ++ throw new EOFException(); ++ int len = (int) ret; ++ byte [] data = new byte[len]; ++ System.arraycopy(temp, 0, data, 0, len); ++ verboseLog("UDP read", data); ++ return data; ++} ++ ++static byte [] ++sendrecv(SocketAddress local, SocketAddress remote, byte [] data, int max, ++ long endTime) ++throws IOException ++{ ++ UDPClient client = new UDPClient(endTime); ++ try { ++ client.bind(local); ++ client.connect(remote); ++ client.send(data); ++ return client.recv(max); ++ } ++ finally { ++ client.cleanup(); ++ } ++} ++ ++static byte [] ++sendrecv(SocketAddress addr, byte [] data, int max, long endTime) ++throws IOException ++{ ++ return sendrecv(null, addr, data, max, endTime); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/UNKRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/UNKRecord.java +new file mode 100644 +index 0000000..91c9697 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/UNKRecord.java +@@ -0,0 +1,54 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++/** ++ * A class implementing Records of unknown and/or unimplemented types. This ++ * class can only be initialized using static Record initializers. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class UNKRecord extends Record { ++ ++private static final long serialVersionUID = -4193583311594626915L; ++ ++private byte [] data; ++ ++UNKRecord() {} ++ ++Record ++getObject() { ++ return new UNKRecord(); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ data = in.readByteArray(); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ throw st.exception("invalid unknown RR encoding"); ++} ++ ++/** Converts this Record to the String "unknown format" */ ++String ++rrToString() { ++ return unknownToString(data); ++} ++ ++/** Returns the contents of this record. */ ++public byte [] ++getData() { ++ return data; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeByteArray(data); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Update.java b/external/asmack/build/src/trunk/org/xbill/DNS/Update.java +new file mode 100644 +index 0000000..02a920b +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Update.java +@@ -0,0 +1,300 @@ ++// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.util.*; ++ ++/** ++ * A helper class for constructing dynamic DNS (DDNS) update messages. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class Update extends Message { ++ ++private Name origin; ++private int dclass; ++ ++/** ++ * Creates an update message. ++ * @param zone The name of the zone being updated. ++ * @param dclass The class of the zone being updated. ++ */ ++public ++Update(Name zone, int dclass) { ++ super(); ++ if (!zone.isAbsolute()) ++ throw new RelativeNameException(zone); ++ DClass.check(dclass); ++ getHeader().setOpcode(Opcode.UPDATE); ++ Record soa = Record.newRecord(zone, Type.SOA, DClass.IN); ++ addRecord(soa, Section.QUESTION); ++ this.origin = zone; ++ this.dclass = dclass; ++} ++ ++/** ++ * Creates an update message. The class is assumed to be IN. ++ * @param zone The name of the zone being updated. ++ */ ++public ++Update(Name zone) { ++ this(zone, DClass.IN); ++} ++ ++private void ++newPrereq(Record rec) { ++ addRecord(rec, Section.PREREQ); ++} ++ ++private void ++newUpdate(Record rec) { ++ addRecord(rec, Section.UPDATE); ++} ++ ++/** ++ * Inserts a prerequisite that the specified name exists; that is, there ++ * exist records with the given name in the zone. ++ */ ++public void ++present(Name name) { ++ newPrereq(Record.newRecord(name, Type.ANY, DClass.ANY, 0)); ++} ++ ++/** ++ * Inserts a prerequisite that the specified rrset exists; that is, there ++ * exist records with the given name and type in the zone. ++ */ ++public void ++present(Name name, int type) { ++ newPrereq(Record.newRecord(name, type, DClass.ANY, 0)); ++} ++ ++/** ++ * Parses a record from the string, and inserts a prerequisite that the ++ * record exists. Due to the way value-dependent prequisites work, the ++ * condition that must be met is that the set of all records with the same ++ * and type in the update message must be identical to the set of all records ++ * with that name and type on the server. ++ * @throws IOException The record could not be parsed. ++ */ ++public void ++present(Name name, int type, String record) throws IOException { ++ newPrereq(Record.fromString(name, type, dclass, 0, record, origin)); ++} ++ ++/** ++ * Parses a record from the tokenizer, and inserts a prerequisite that the ++ * record exists. Due to the way value-dependent prequisites work, the ++ * condition that must be met is that the set of all records with the same ++ * and type in the update message must be identical to the set of all records ++ * with that name and type on the server. ++ * @throws IOException The record could not be parsed. ++ */ ++public void ++present(Name name, int type, Tokenizer tokenizer) throws IOException { ++ newPrereq(Record.fromString(name, type, dclass, 0, tokenizer, origin)); ++} ++ ++/** ++ * Inserts a prerequisite that the specified record exists. Due to the way ++ * value-dependent prequisites work, the condition that must be met is that ++ * the set of all records with the same and type in the update message must ++ * be identical to the set of all records with that name and type on the server. ++ */ ++public void ++present(Record record) { ++ newPrereq(record); ++} ++ ++/** ++ * Inserts a prerequisite that the specified name does not exist; that is, ++ * there are no records with the given name in the zone. ++ */ ++public void ++absent(Name name) { ++ newPrereq(Record.newRecord(name, Type.ANY, DClass.NONE, 0)); ++} ++ ++/** ++ * Inserts a prerequisite that the specified rrset does not exist; that is, ++ * there are no records with the given name and type in the zone. ++ */ ++public void ++absent(Name name, int type) { ++ newPrereq(Record.newRecord(name, type, DClass.NONE, 0)); ++} ++ ++/** ++ * Parses a record from the string, and indicates that the record ++ * should be inserted into the zone. ++ * @throws IOException The record could not be parsed. ++ */ ++public void ++add(Name name, int type, long ttl, String record) throws IOException { ++ newUpdate(Record.fromString(name, type, dclass, ttl, record, origin)); ++} ++ ++/** ++ * Parses a record from the tokenizer, and indicates that the record ++ * should be inserted into the zone. ++ * @throws IOException The record could not be parsed. ++ */ ++public void ++add(Name name, int type, long ttl, Tokenizer tokenizer) throws IOException { ++ newUpdate(Record.fromString(name, type, dclass, ttl, tokenizer, ++ origin)); ++} ++ ++/** ++ * Indicates that the record should be inserted into the zone. ++ */ ++public void ++add(Record record) { ++ newUpdate(record); ++} ++ ++/** ++ * Indicates that the records should be inserted into the zone. ++ */ ++public void ++add(Record [] records) { ++ for (int i = 0; i < records.length; i++) ++ add(records[i]); ++} ++ ++/** ++ * Indicates that all of the records in the rrset should be inserted into the ++ * zone. ++ */ ++public void ++add(RRset rrset) { ++ for (Iterator it = rrset.rrs(); it.hasNext(); ) ++ add((Record) it.next()); ++} ++ ++/** ++ * Indicates that all records with the given name should be deleted from ++ * the zone. ++ */ ++public void ++delete(Name name) { ++ newUpdate(Record.newRecord(name, Type.ANY, DClass.ANY, 0)); ++} ++ ++/** ++ * Indicates that all records with the given name and type should be deleted ++ * from the zone. ++ */ ++public void ++delete(Name name, int type) { ++ newUpdate(Record.newRecord(name, type, DClass.ANY, 0)); ++} ++ ++/** ++ * Parses a record from the string, and indicates that the record ++ * should be deleted from the zone. ++ * @throws IOException The record could not be parsed. ++ */ ++public void ++delete(Name name, int type, String record) throws IOException { ++ newUpdate(Record.fromString(name, type, DClass.NONE, 0, record, ++ origin)); ++} ++ ++/** ++ * Parses a record from the tokenizer, and indicates that the record ++ * should be deleted from the zone. ++ * @throws IOException The record could not be parsed. ++ */ ++public void ++delete(Name name, int type, Tokenizer tokenizer) throws IOException { ++ newUpdate(Record.fromString(name, type, DClass.NONE, 0, tokenizer, ++ origin)); ++} ++ ++/** ++ * Indicates that the specified record should be deleted from the zone. ++ */ ++public void ++delete(Record record) { ++ newUpdate(record.withDClass(DClass.NONE, 0)); ++} ++ ++/** ++ * Indicates that the records should be deleted from the zone. ++ */ ++public void ++delete(Record [] records) { ++ for (int i = 0; i < records.length; i++) ++ delete(records[i]); ++} ++ ++/** ++ * Indicates that all of the records in the rrset should be deleted from the ++ * zone. ++ */ ++public void ++delete(RRset rrset) { ++ for (Iterator it = rrset.rrs(); it.hasNext(); ) ++ delete((Record) it.next()); ++} ++ ++/** ++ * Parses a record from the string, and indicates that the record ++ * should be inserted into the zone replacing any other records with the ++ * same name and type. ++ * @throws IOException The record could not be parsed. ++ */ ++public void ++replace(Name name, int type, long ttl, String record) throws IOException { ++ delete(name, type); ++ add(name, type, ttl, record); ++} ++ ++/** ++ * Parses a record from the tokenizer, and indicates that the record ++ * should be inserted into the zone replacing any other records with the ++ * same name and type. ++ * @throws IOException The record could not be parsed. ++ */ ++public void ++replace(Name name, int type, long ttl, Tokenizer tokenizer) throws IOException ++{ ++ delete(name, type); ++ add(name, type, ttl, tokenizer); ++} ++ ++/** ++ * Indicates that the record should be inserted into the zone replacing any ++ * other records with the same name and type. ++ */ ++public void ++replace(Record record) { ++ delete(record.getName(), record.getType()); ++ add(record); ++} ++ ++/** ++ * Indicates that the records should be inserted into the zone replacing any ++ * other records with the same name and type as each one. ++ */ ++public void ++replace(Record [] records) { ++ for (int i = 0; i < records.length; i++) ++ replace(records[i]); ++} ++ ++/** ++ * Indicates that all of the records in the rrset should be inserted into the ++ * zone replacing any other records with the same name and type. ++ */ ++public void ++replace(RRset rrset) { ++ delete(rrset.getName(), rrset.getType()); ++ for (Iterator it = rrset.rrs(); it.hasNext(); ) ++ add((Record) it.next()); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/WKSRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/WKSRecord.java +new file mode 100644 +index 0000000..10b61be +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/WKSRecord.java +@@ -0,0 +1,719 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.net.*; ++import java.io.*; ++import java.util.*; ++ ++/** ++ * Well Known Services - Lists services offered by this host. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class WKSRecord extends Record { ++ ++private static final long serialVersionUID = -9104259763909119805L; ++ ++public static class Protocol { ++ /** ++ * IP protocol identifiers. This is basically copied out of RFC 1010. ++ */ ++ ++ private Protocol() {} ++ ++ /** Internet Control Message */ ++ public static final int ICMP = 1; ++ ++ /** Internet Group Management */ ++ public static final int IGMP = 2; ++ ++ /** Gateway-to-Gateway */ ++ public static final int GGP = 3; ++ ++ /** Stream */ ++ public static final int ST = 5; ++ ++ /** Transmission Control */ ++ public static final int TCP = 6; ++ ++ /** UCL */ ++ public static final int UCL = 7; ++ ++ /** Exterior Gateway Protocol */ ++ public static final int EGP = 8; ++ ++ /** any private interior gateway */ ++ public static final int IGP = 9; ++ ++ /** BBN RCC Monitoring */ ++ public static final int BBN_RCC_MON = 10; ++ ++ /** Network Voice Protocol */ ++ public static final int NVP_II = 11; ++ ++ /** PUP */ ++ public static final int PUP = 12; ++ ++ /** ARGUS */ ++ public static final int ARGUS = 13; ++ ++ /** EMCON */ ++ public static final int EMCON = 14; ++ ++ /** Cross Net Debugger */ ++ public static final int XNET = 15; ++ ++ /** Chaos */ ++ public static final int CHAOS = 16; ++ ++ /** User Datagram */ ++ public static final int UDP = 17; ++ ++ /** Multiplexing */ ++ public static final int MUX = 18; ++ ++ /** DCN Measurement Subsystems */ ++ public static final int DCN_MEAS = 19; ++ ++ /** Host Monitoring */ ++ public static final int HMP = 20; ++ ++ /** Packet Radio Measurement */ ++ public static final int PRM = 21; ++ ++ /** XEROX NS IDP */ ++ public static final int XNS_IDP = 22; ++ ++ /** Trunk-1 */ ++ public static final int TRUNK_1 = 23; ++ ++ /** Trunk-2 */ ++ public static final int TRUNK_2 = 24; ++ ++ /** Leaf-1 */ ++ public static final int LEAF_1 = 25; ++ ++ /** Leaf-2 */ ++ public static final int LEAF_2 = 26; ++ ++ /** Reliable Data Protocol */ ++ public static final int RDP = 27; ++ ++ /** Internet Reliable Transaction */ ++ public static final int IRTP = 28; ++ ++ /** ISO Transport Protocol Class 4 */ ++ public static final int ISO_TP4 = 29; ++ ++ /** Bulk Data Transfer Protocol */ ++ public static final int NETBLT = 30; ++ ++ /** MFE Network Services Protocol */ ++ public static final int MFE_NSP = 31; ++ ++ /** MERIT Internodal Protocol */ ++ public static final int MERIT_INP = 32; ++ ++ /** Sequential Exchange Protocol */ ++ public static final int SEP = 33; ++ ++ /** CFTP */ ++ public static final int CFTP = 62; ++ ++ /** SATNET and Backroom EXPAK */ ++ public static final int SAT_EXPAK = 64; ++ ++ /** MIT Subnet Support */ ++ public static final int MIT_SUBNET = 65; ++ ++ /** MIT Remote Virtual Disk Protocol */ ++ public static final int RVD = 66; ++ ++ /** Internet Pluribus Packet Core */ ++ public static final int IPPC = 67; ++ ++ /** SATNET Monitoring */ ++ public static final int SAT_MON = 69; ++ ++ /** Internet Packet Core Utility */ ++ public static final int IPCV = 71; ++ ++ /** Backroom SATNET Monitoring */ ++ public static final int BR_SAT_MON = 76; ++ ++ /** WIDEBAND Monitoring */ ++ public static final int WB_MON = 78; ++ ++ /** WIDEBAND EXPAK */ ++ public static final int WB_EXPAK = 79; ++ ++ private static Mnemonic protocols = new Mnemonic("IP protocol", ++ Mnemonic.CASE_LOWER); ++ ++ static { ++ protocols.setMaximum(0xFF); ++ protocols.setNumericAllowed(true); ++ ++ protocols.add(ICMP, "icmp"); ++ protocols.add(IGMP, "igmp"); ++ protocols.add(GGP, "ggp"); ++ protocols.add(ST, "st"); ++ protocols.add(TCP, "tcp"); ++ protocols.add(UCL, "ucl"); ++ protocols.add(EGP, "egp"); ++ protocols.add(IGP, "igp"); ++ protocols.add(BBN_RCC_MON, "bbn-rcc-mon"); ++ protocols.add(NVP_II, "nvp-ii"); ++ protocols.add(PUP, "pup"); ++ protocols.add(ARGUS, "argus"); ++ protocols.add(EMCON, "emcon"); ++ protocols.add(XNET, "xnet"); ++ protocols.add(CHAOS, "chaos"); ++ protocols.add(UDP, "udp"); ++ protocols.add(MUX, "mux"); ++ protocols.add(DCN_MEAS, "dcn-meas"); ++ protocols.add(HMP, "hmp"); ++ protocols.add(PRM, "prm"); ++ protocols.add(XNS_IDP, "xns-idp"); ++ protocols.add(TRUNK_1, "trunk-1"); ++ protocols.add(TRUNK_2, "trunk-2"); ++ protocols.add(LEAF_1, "leaf-1"); ++ protocols.add(LEAF_2, "leaf-2"); ++ protocols.add(RDP, "rdp"); ++ protocols.add(IRTP, "irtp"); ++ protocols.add(ISO_TP4, "iso-tp4"); ++ protocols.add(NETBLT, "netblt"); ++ protocols.add(MFE_NSP, "mfe-nsp"); ++ protocols.add(MERIT_INP, "merit-inp"); ++ protocols.add(SEP, "sep"); ++ protocols.add(CFTP, "cftp"); ++ protocols.add(SAT_EXPAK, "sat-expak"); ++ protocols.add(MIT_SUBNET, "mit-subnet"); ++ protocols.add(RVD, "rvd"); ++ protocols.add(IPPC, "ippc"); ++ protocols.add(SAT_MON, "sat-mon"); ++ protocols.add(IPCV, "ipcv"); ++ protocols.add(BR_SAT_MON, "br-sat-mon"); ++ protocols.add(WB_MON, "wb-mon"); ++ protocols.add(WB_EXPAK, "wb-expak"); ++ } ++ ++ /** ++ * Converts an IP protocol value into its textual representation ++ */ ++ public static String ++ string(int type) { ++ return protocols.getText(type); ++ } ++ ++ /** ++ * Converts a textual representation of an IP protocol into its ++ * numeric code. Integers in the range 0..255 are also accepted. ++ * @param s The textual representation of the protocol ++ * @return The protocol code, or -1 on error. ++ */ ++ public static int ++ value(String s) { ++ return protocols.getValue(s); ++ } ++} ++ ++public static class Service { ++ /** ++ * TCP/UDP services. This is basically copied out of RFC 1010, ++ * with MIT-ML-DEV removed, as it is not unique, and the description ++ * of SWIFT-RVF fixed. ++ */ ++ ++ private Service() {} ++ ++ /** Remote Job Entry */ ++ public static final int RJE = 5; ++ ++ /** Echo */ ++ public static final int ECHO = 7; ++ ++ /** Discard */ ++ public static final int DISCARD = 9; ++ ++ /** Active Users */ ++ public static final int USERS = 11; ++ ++ /** Daytime */ ++ public static final int DAYTIME = 13; ++ ++ /** Quote of the Day */ ++ public static final int QUOTE = 17; ++ ++ /** Character Generator */ ++ public static final int CHARGEN = 19; ++ ++ /** File Transfer [Default Data] */ ++ public static final int FTP_DATA = 20; ++ ++ /** File Transfer [Control] */ ++ public static final int FTP = 21; ++ ++ /** Telnet */ ++ public static final int TELNET = 23; ++ ++ /** Simple Mail Transfer */ ++ public static final int SMTP = 25; ++ ++ /** NSW User System FE */ ++ public static final int NSW_FE = 27; ++ ++ /** MSG ICP */ ++ public static final int MSG_ICP = 29; ++ ++ /** MSG Authentication */ ++ public static final int MSG_AUTH = 31; ++ ++ /** Display Support Protocol */ ++ public static final int DSP = 33; ++ ++ /** Time */ ++ public static final int TIME = 37; ++ ++ /** Resource Location Protocol */ ++ public static final int RLP = 39; ++ ++ /** Graphics */ ++ public static final int GRAPHICS = 41; ++ ++ /** Host Name Server */ ++ public static final int NAMESERVER = 42; ++ ++ /** Who Is */ ++ public static final int NICNAME = 43; ++ ++ /** MPM FLAGS Protocol */ ++ public static final int MPM_FLAGS = 44; ++ ++ /** Message Processing Module [recv] */ ++ public static final int MPM = 45; ++ ++ /** MPM [default send] */ ++ public static final int MPM_SND = 46; ++ ++ /** NI FTP */ ++ public static final int NI_FTP = 47; ++ ++ /** Login Host Protocol */ ++ public static final int LOGIN = 49; ++ ++ /** IMP Logical Address Maintenance */ ++ public static final int LA_MAINT = 51; ++ ++ /** Domain Name Server */ ++ public static final int DOMAIN = 53; ++ ++ /** ISI Graphics Language */ ++ public static final int ISI_GL = 55; ++ ++ /** NI MAIL */ ++ public static final int NI_MAIL = 61; ++ ++ /** VIA Systems - FTP */ ++ public static final int VIA_FTP = 63; ++ ++ /** TACACS-Database Service */ ++ public static final int TACACS_DS = 65; ++ ++ /** Bootstrap Protocol Server */ ++ public static final int BOOTPS = 67; ++ ++ /** Bootstrap Protocol Client */ ++ public static final int BOOTPC = 68; ++ ++ /** Trivial File Transfer */ ++ public static final int TFTP = 69; ++ ++ /** Remote Job Service */ ++ public static final int NETRJS_1 = 71; ++ ++ /** Remote Job Service */ ++ public static final int NETRJS_2 = 72; ++ ++ /** Remote Job Service */ ++ public static final int NETRJS_3 = 73; ++ ++ /** Remote Job Service */ ++ public static final int NETRJS_4 = 74; ++ ++ /** Finger */ ++ public static final int FINGER = 79; ++ ++ /** HOSTS2 Name Server */ ++ public static final int HOSTS2_NS = 81; ++ ++ /** SU/MIT Telnet Gateway */ ++ public static final int SU_MIT_TG = 89; ++ ++ /** MIT Dover Spooler */ ++ public static final int MIT_DOV = 91; ++ ++ /** Device Control Protocol */ ++ public static final int DCP = 93; ++ ++ /** SUPDUP */ ++ public static final int SUPDUP = 95; ++ ++ /** Swift Remote Virtual File Protocol */ ++ public static final int SWIFT_RVF = 97; ++ ++ /** TAC News */ ++ public static final int TACNEWS = 98; ++ ++ /** Metagram Relay */ ++ public static final int METAGRAM = 99; ++ ++ /** NIC Host Name Server */ ++ public static final int HOSTNAME = 101; ++ ++ /** ISO-TSAP */ ++ public static final int ISO_TSAP = 102; ++ ++ /** X400 */ ++ public static final int X400 = 103; ++ ++ /** X400-SND */ ++ public static final int X400_SND = 104; ++ ++ /** Mailbox Name Nameserver */ ++ public static final int CSNET_NS = 105; ++ ++ /** Remote Telnet Service */ ++ public static final int RTELNET = 107; ++ ++ /** Post Office Protocol - Version 2 */ ++ public static final int POP_2 = 109; ++ ++ /** SUN Remote Procedure Call */ ++ public static final int SUNRPC = 111; ++ ++ /** Authentication Service */ ++ public static final int AUTH = 113; ++ ++ /** Simple File Transfer Protocol */ ++ public static final int SFTP = 115; ++ ++ /** UUCP Path Service */ ++ public static final int UUCP_PATH = 117; ++ ++ /** Network News Transfer Protocol */ ++ public static final int NNTP = 119; ++ ++ /** HYDRA Expedited Remote Procedure */ ++ public static final int ERPC = 121; ++ ++ /** Network Time Protocol */ ++ public static final int NTP = 123; ++ ++ /** Locus PC-Interface Net Map Server */ ++ public static final int LOCUS_MAP = 125; ++ ++ /** Locus PC-Interface Conn Server */ ++ public static final int LOCUS_CON = 127; ++ ++ /** Password Generator Protocol */ ++ public static final int PWDGEN = 129; ++ ++ /** CISCO FNATIVE */ ++ public static final int CISCO_FNA = 130; ++ ++ /** CISCO TNATIVE */ ++ public static final int CISCO_TNA = 131; ++ ++ /** CISCO SYSMAINT */ ++ public static final int CISCO_SYS = 132; ++ ++ /** Statistics Service */ ++ public static final int STATSRV = 133; ++ ++ /** INGRES-NET Service */ ++ public static final int INGRES_NET = 134; ++ ++ /** Location Service */ ++ public static final int LOC_SRV = 135; ++ ++ /** PROFILE Naming System */ ++ public static final int PROFILE = 136; ++ ++ /** NETBIOS Name Service */ ++ public static final int NETBIOS_NS = 137; ++ ++ /** NETBIOS Datagram Service */ ++ public static final int NETBIOS_DGM = 138; ++ ++ /** NETBIOS Session Service */ ++ public static final int NETBIOS_SSN = 139; ++ ++ /** EMFIS Data Service */ ++ public static final int EMFIS_DATA = 140; ++ ++ /** EMFIS Control Service */ ++ public static final int EMFIS_CNTL = 141; ++ ++ /** Britton-Lee IDM */ ++ public static final int BL_IDM = 142; ++ ++ /** Survey Measurement */ ++ public static final int SUR_MEAS = 243; ++ ++ /** LINK */ ++ public static final int LINK = 245; ++ ++ private static Mnemonic services = new Mnemonic("TCP/UDP service", ++ Mnemonic.CASE_LOWER); ++ ++ static { ++ services.setMaximum(0xFFFF); ++ services.setNumericAllowed(true); ++ ++ services.add(RJE, "rje"); ++ services.add(ECHO, "echo"); ++ services.add(DISCARD, "discard"); ++ services.add(USERS, "users"); ++ services.add(DAYTIME, "daytime"); ++ services.add(QUOTE, "quote"); ++ services.add(CHARGEN, "chargen"); ++ services.add(FTP_DATA, "ftp-data"); ++ services.add(FTP, "ftp"); ++ services.add(TELNET, "telnet"); ++ services.add(SMTP, "smtp"); ++ services.add(NSW_FE, "nsw-fe"); ++ services.add(MSG_ICP, "msg-icp"); ++ services.add(MSG_AUTH, "msg-auth"); ++ services.add(DSP, "dsp"); ++ services.add(TIME, "time"); ++ services.add(RLP, "rlp"); ++ services.add(GRAPHICS, "graphics"); ++ services.add(NAMESERVER, "nameserver"); ++ services.add(NICNAME, "nicname"); ++ services.add(MPM_FLAGS, "mpm-flags"); ++ services.add(MPM, "mpm"); ++ services.add(MPM_SND, "mpm-snd"); ++ services.add(NI_FTP, "ni-ftp"); ++ services.add(LOGIN, "login"); ++ services.add(LA_MAINT, "la-maint"); ++ services.add(DOMAIN, "domain"); ++ services.add(ISI_GL, "isi-gl"); ++ services.add(NI_MAIL, "ni-mail"); ++ services.add(VIA_FTP, "via-ftp"); ++ services.add(TACACS_DS, "tacacs-ds"); ++ services.add(BOOTPS, "bootps"); ++ services.add(BOOTPC, "bootpc"); ++ services.add(TFTP, "tftp"); ++ services.add(NETRJS_1, "netrjs-1"); ++ services.add(NETRJS_2, "netrjs-2"); ++ services.add(NETRJS_3, "netrjs-3"); ++ services.add(NETRJS_4, "netrjs-4"); ++ services.add(FINGER, "finger"); ++ services.add(HOSTS2_NS, "hosts2-ns"); ++ services.add(SU_MIT_TG, "su-mit-tg"); ++ services.add(MIT_DOV, "mit-dov"); ++ services.add(DCP, "dcp"); ++ services.add(SUPDUP, "supdup"); ++ services.add(SWIFT_RVF, "swift-rvf"); ++ services.add(TACNEWS, "tacnews"); ++ services.add(METAGRAM, "metagram"); ++ services.add(HOSTNAME, "hostname"); ++ services.add(ISO_TSAP, "iso-tsap"); ++ services.add(X400, "x400"); ++ services.add(X400_SND, "x400-snd"); ++ services.add(CSNET_NS, "csnet-ns"); ++ services.add(RTELNET, "rtelnet"); ++ services.add(POP_2, "pop-2"); ++ services.add(SUNRPC, "sunrpc"); ++ services.add(AUTH, "auth"); ++ services.add(SFTP, "sftp"); ++ services.add(UUCP_PATH, "uucp-path"); ++ services.add(NNTP, "nntp"); ++ services.add(ERPC, "erpc"); ++ services.add(NTP, "ntp"); ++ services.add(LOCUS_MAP, "locus-map"); ++ services.add(LOCUS_CON, "locus-con"); ++ services.add(PWDGEN, "pwdgen"); ++ services.add(CISCO_FNA, "cisco-fna"); ++ services.add(CISCO_TNA, "cisco-tna"); ++ services.add(CISCO_SYS, "cisco-sys"); ++ services.add(STATSRV, "statsrv"); ++ services.add(INGRES_NET, "ingres-net"); ++ services.add(LOC_SRV, "loc-srv"); ++ services.add(PROFILE, "profile"); ++ services.add(NETBIOS_NS, "netbios-ns"); ++ services.add(NETBIOS_DGM, "netbios-dgm"); ++ services.add(NETBIOS_SSN, "netbios-ssn"); ++ services.add(EMFIS_DATA, "emfis-data"); ++ services.add(EMFIS_CNTL, "emfis-cntl"); ++ services.add(BL_IDM, "bl-idm"); ++ services.add(SUR_MEAS, "sur-meas"); ++ services.add(LINK, "link"); ++ } ++ ++ /** ++ * Converts a TCP/UDP service port number into its textual ++ * representation. ++ */ ++ public static String ++ string(int type) { ++ return services.getText(type); ++ } ++ ++ /** ++ * Converts a textual representation of a TCP/UDP service into its ++ * port number. Integers in the range 0..65535 are also accepted. ++ * @param s The textual representation of the service. ++ * @return The port number, or -1 on error. ++ */ ++ public static int ++ value(String s) { ++ return services.getValue(s); ++ } ++} ++private byte [] address; ++private int protocol; ++private int [] services; ++ ++WKSRecord() {} ++ ++Record ++getObject() { ++ return new WKSRecord(); ++} ++ ++/** ++ * Creates a WKS Record from the given data ++ * @param address The IP address ++ * @param protocol The IP protocol number ++ * @param services An array of supported services, represented by port number. ++ */ ++public ++WKSRecord(Name name, int dclass, long ttl, InetAddress address, int protocol, ++ int [] services) ++{ ++ super(name, Type.WKS, dclass, ttl); ++ if (Address.familyOf(address) != Address.IPv4) ++ throw new IllegalArgumentException("invalid IPv4 address"); ++ this.address = address.getAddress(); ++ this.protocol = checkU8("protocol", protocol); ++ for (int i = 0; i < services.length; i++) { ++ checkU16("service", services[i]); ++ } ++ this.services = new int[services.length]; ++ System.arraycopy(services, 0, this.services, 0, services.length); ++ Arrays.sort(this.services); ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ address = in.readByteArray(4); ++ protocol = in.readU8(); ++ byte [] array = in.readByteArray(); ++ List list = new ArrayList(); ++ for (int i = 0; i < array.length; i++) { ++ for (int j = 0; j < 8; j++) { ++ int octet = array[i] & 0xFF; ++ if ((octet & (1 << (7 - j))) != 0) { ++ list.add(new Integer(i * 8 + j)); ++ } ++ } ++ } ++ services = new int[list.size()]; ++ for (int i = 0; i < list.size(); i++) { ++ services[i] = ((Integer) list.get(i)).intValue(); ++ } ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ String s = st.getString(); ++ address = Address.toByteArray(s, Address.IPv4); ++ if (address == null) ++ throw st.exception("invalid address"); ++ ++ s = st.getString(); ++ protocol = Protocol.value(s); ++ if (protocol < 0) { ++ throw st.exception("Invalid IP protocol: " + s); ++ } ++ ++ List list = new ArrayList(); ++ while (true) { ++ Tokenizer.Token t = st.get(); ++ if (!t.isString()) ++ break; ++ int service = Service.value(t.value); ++ if (service < 0) { ++ throw st.exception("Invalid TCP/UDP service: " + ++ t.value); ++ } ++ list.add(new Integer(service)); ++ } ++ st.unget(); ++ services = new int[list.size()]; ++ for (int i = 0; i < list.size(); i++) { ++ services[i] = ((Integer) list.get(i)).intValue(); ++ } ++} ++ ++/** ++ * Converts rdata to a String ++ */ ++String ++rrToString() { ++ StringBuffer sb = new StringBuffer(); ++ sb.append(Address.toDottedQuad(address)); ++ sb.append(" "); ++ sb.append(protocol); ++ for (int i = 0; i < services.length; i++) { ++ sb.append(" " + services[i]); ++ } ++ return sb.toString(); ++} ++ ++/** ++ * Returns the IP address. ++ */ ++public InetAddress ++getAddress() { ++ try { ++ return InetAddress.getByAddress(address); ++ } catch (UnknownHostException e) { ++ return null; ++ } ++} ++ ++/** ++ * Returns the IP protocol. ++ */ ++public int ++getProtocol() { ++ return protocol; ++} ++ ++/** ++ * Returns the services provided by the host on the specified address. ++ */ ++public int [] ++getServices() { ++ return services; ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeByteArray(address); ++ out.writeU8(protocol); ++ int highestPort = services[services.length - 1]; ++ byte [] array = new byte[highestPort / 8 + 1]; ++ for (int i = 0; i < services.length; i++) { ++ int port = services[i]; ++ array[port / 8] |= (1 << (7 - port % 8)); ++ } ++ out.writeByteArray(array); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/WireParseException.java b/external/asmack/build/src/trunk/org/xbill/DNS/WireParseException.java +new file mode 100644 +index 0000000..2842731 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/WireParseException.java +@@ -0,0 +1,31 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++/** ++ * An exception thrown when a DNS message is invalid. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class WireParseException extends IOException { ++ ++public ++WireParseException() { ++ super(); ++} ++ ++public ++WireParseException(String s) { ++ super(s); ++} ++ ++public ++WireParseException(String s, Throwable cause) { ++ super(s); ++ initCause(cause); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/X25Record.java b/external/asmack/build/src/trunk/org/xbill/DNS/X25Record.java +new file mode 100644 +index 0000000..1349a1e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/X25Record.java +@@ -0,0 +1,86 @@ ++// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++ ++/** ++ * X25 - identifies the PSDN (Public Switched Data Network) address in the ++ * X.121 numbering plan associated with a name. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class X25Record extends Record { ++ ++private static final long serialVersionUID = 4267576252335579764L; ++ ++private byte [] address; ++ ++X25Record() {} ++ ++Record ++getObject() { ++ return new X25Record(); ++} ++ ++private static final byte [] ++checkAndConvertAddress(String address) { ++ int length = address.length(); ++ byte [] out = new byte [length]; ++ for (int i = 0; i < length; i++) { ++ char c = address.charAt(i); ++ if (!Character.isDigit(c)) ++ return null; ++ out[i] = (byte) c; ++ } ++ return out; ++} ++ ++/** ++ * Creates an X25 Record from the given data ++ * @param address The X.25 PSDN address. ++ * @throws IllegalArgumentException The address is not a valid PSDN address. ++ */ ++public ++X25Record(Name name, int dclass, long ttl, String address) { ++ super(name, Type.X25, dclass, ttl); ++ this.address = checkAndConvertAddress(address); ++ if (this.address == null) { ++ throw new IllegalArgumentException("invalid PSDN address " + ++ address); ++ } ++} ++ ++void ++rrFromWire(DNSInput in) throws IOException { ++ address = in.readCountedString(); ++} ++ ++void ++rdataFromString(Tokenizer st, Name origin) throws IOException { ++ String addr = st.getString(); ++ this.address = checkAndConvertAddress(addr); ++ if (this.address == null) ++ throw st.exception("invalid PSDN address " + addr); ++} ++ ++/** ++ * Returns the X.25 PSDN address. ++ */ ++public String ++getAddress() { ++ return byteArrayToString(address, false); ++} ++ ++void ++rrToWire(DNSOutput out, Compression c, boolean canonical) { ++ out.writeCountedString(address); ++} ++ ++String ++rrToString() { ++ return byteArrayToString(address, true); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Zone.java b/external/asmack/build/src/trunk/org/xbill/DNS/Zone.java +new file mode 100644 +index 0000000..866be77 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/Zone.java +@@ -0,0 +1,559 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.util.*; ++ ++/** ++ * A DNS Zone. This encapsulates all data related to a Zone, and provides ++ * convenient lookup methods. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class Zone implements Serializable { ++ ++private static final long serialVersionUID = -9220510891189510942L; ++ ++/** A primary zone */ ++public static final int PRIMARY = 1; ++ ++/** A secondary zone */ ++public static final int SECONDARY = 2; ++ ++private Map data; ++private Name origin; ++private Object originNode; ++private int dclass = DClass.IN; ++private RRset NS; ++private SOARecord SOA; ++private boolean hasWild; ++ ++class ZoneIterator implements Iterator { ++ private Iterator zentries; ++ private RRset [] current; ++ private int count; ++ private boolean wantLastSOA; ++ ++ ZoneIterator(boolean axfr) { ++ synchronized (Zone.this) { ++ zentries = data.entrySet().iterator(); ++ } ++ wantLastSOA = axfr; ++ RRset [] sets = allRRsets(originNode); ++ current = new RRset[sets.length]; ++ for (int i = 0, j = 2; i < sets.length; i++) { ++ int type = sets[i].getType(); ++ if (type == Type.SOA) ++ current[0] = sets[i]; ++ else if (type == Type.NS) ++ current[1] = sets[i]; ++ else ++ current[j++] = sets[i]; ++ } ++ } ++ ++ public boolean ++ hasNext() { ++ return (current != null || wantLastSOA); ++ } ++ ++ public Object ++ next() { ++ if (!hasNext()) { ++ throw new NoSuchElementException(); ++ } ++ if (current == null) { ++ wantLastSOA = false; ++ return oneRRset(originNode, Type.SOA); ++ } ++ Object set = current[count++]; ++ if (count == current.length) { ++ current = null; ++ while (zentries.hasNext()) { ++ Map.Entry entry = (Map.Entry) zentries.next(); ++ if (entry.getKey().equals(origin)) ++ continue; ++ RRset [] sets = allRRsets(entry.getValue()); ++ if (sets.length == 0) ++ continue; ++ current = sets; ++ count = 0; ++ break; ++ } ++ } ++ return set; ++ } ++ ++ public void ++ remove() { ++ throw new UnsupportedOperationException(); ++ } ++} ++ ++private void ++validate() throws IOException { ++ originNode = exactName(origin); ++ if (originNode == null) ++ throw new IOException(origin + ": no data specified"); ++ ++ RRset rrset = oneRRset(originNode, Type.SOA); ++ if (rrset == null || rrset.size() != 1) ++ throw new IOException(origin + ++ ": exactly 1 SOA must be specified"); ++ Iterator it = rrset.rrs(); ++ SOA = (SOARecord) it.next(); ++ ++ NS = oneRRset(originNode, Type.NS); ++ if (NS == null) ++ throw new IOException(origin + ": no NS set specified"); ++} ++ ++private final void ++maybeAddRecord(Record record) throws IOException { ++ int rtype = record.getType(); ++ Name name = record.getName(); ++ ++ if (rtype == Type.SOA && !name.equals(origin)) { ++ throw new IOException("SOA owner " + name + ++ " does not match zone origin " + ++ origin); ++ } ++ if (name.subdomain(origin)) ++ addRecord(record); ++} ++ ++/** ++ * Creates a Zone from the records in the specified master file. ++ * @param zone The name of the zone. ++ * @param file The master file to read from. ++ * @see Master ++ */ ++public ++Zone(Name zone, String file) throws IOException { ++ data = new TreeMap(); ++ ++ if (zone == null) ++ throw new IllegalArgumentException("no zone name specified"); ++ Master m = new Master(file, zone); ++ Record record; ++ ++ origin = zone; ++ while ((record = m.nextRecord()) != null) ++ maybeAddRecord(record); ++ validate(); ++} ++ ++/** ++ * Creates a Zone from an array of records. ++ * @param zone The name of the zone. ++ * @param records The records to add to the zone. ++ * @see Master ++ */ ++public ++Zone(Name zone, Record [] records) throws IOException { ++ data = new TreeMap(); ++ ++ if (zone == null) ++ throw new IllegalArgumentException("no zone name specified"); ++ origin = zone; ++ for (int i = 0; i < records.length; i++) ++ maybeAddRecord(records[i]); ++ validate(); ++} ++ ++private void ++fromXFR(ZoneTransferIn xfrin) throws IOException, ZoneTransferException { ++ data = new TreeMap(); ++ ++ origin = xfrin.getName(); ++ List records = xfrin.run(); ++ for (Iterator it = records.iterator(); it.hasNext(); ) { ++ Record record = (Record) it.next(); ++ maybeAddRecord(record); ++ } ++ if (!xfrin.isAXFR()) ++ throw new IllegalArgumentException("zones can only be " + ++ "created from AXFRs"); ++ validate(); ++} ++ ++/** ++ * Creates a Zone by doing the specified zone transfer. ++ * @param xfrin The incoming zone transfer to execute. ++ * @see ZoneTransferIn ++ */ ++public ++Zone(ZoneTransferIn xfrin) throws IOException, ZoneTransferException { ++ fromXFR(xfrin); ++} ++ ++/** ++ * Creates a Zone by performing a zone transfer to the specified host. ++ * @see ZoneTransferIn ++ */ ++public ++Zone(Name zone, int dclass, String remote) ++throws IOException, ZoneTransferException ++{ ++ ZoneTransferIn xfrin = ZoneTransferIn.newAXFR(zone, remote, null); ++ xfrin.setDClass(dclass); ++ fromXFR(xfrin); ++} ++ ++/** Returns the Zone's origin */ ++public Name ++getOrigin() { ++ return origin; ++} ++ ++/** Returns the Zone origin's NS records */ ++public RRset ++getNS() { ++ return NS; ++} ++ ++/** Returns the Zone's SOA record */ ++public SOARecord ++getSOA() { ++ return SOA; ++} ++ ++/** Returns the Zone's class */ ++public int ++getDClass() { ++ return dclass; ++} ++ ++private synchronized Object ++exactName(Name name) { ++ return data.get(name); ++} ++ ++private synchronized RRset [] ++allRRsets(Object types) { ++ if (types instanceof List) { ++ List typelist = (List) types; ++ return (RRset []) typelist.toArray(new RRset[typelist.size()]); ++ } else { ++ RRset set = (RRset) types; ++ return new RRset [] {set}; ++ } ++} ++ ++private synchronized RRset ++oneRRset(Object types, int type) { ++ if (type == Type.ANY) ++ throw new IllegalArgumentException("oneRRset(ANY)"); ++ if (types instanceof List) { ++ List list = (List) types; ++ for (int i = 0; i < list.size(); i++) { ++ RRset set = (RRset) list.get(i); ++ if (set.getType() == type) ++ return set; ++ } ++ } else { ++ RRset set = (RRset) types; ++ if (set.getType() == type) ++ return set; ++ } ++ return null; ++} ++ ++private synchronized RRset ++findRRset(Name name, int type) { ++ Object types = exactName(name); ++ if (types == null) ++ return null; ++ return oneRRset(types, type); ++} ++ ++private synchronized void ++addRRset(Name name, RRset rrset) { ++ if (!hasWild && name.isWild()) ++ hasWild = true; ++ Object types = data.get(name); ++ if (types == null) { ++ data.put(name, rrset); ++ return; ++ } ++ int rtype = rrset.getType(); ++ if (types instanceof List) { ++ List list = (List) types; ++ for (int i = 0; i < list.size(); i++) { ++ RRset set = (RRset) list.get(i); ++ if (set.getType() == rtype) { ++ list.set(i, rrset); ++ return; ++ } ++ } ++ list.add(rrset); ++ } else { ++ RRset set = (RRset) types; ++ if (set.getType() == rtype) ++ data.put(name, rrset); ++ else { ++ LinkedList list = new LinkedList(); ++ list.add(set); ++ list.add(rrset); ++ data.put(name, list); ++ } ++ } ++} ++ ++private synchronized void ++removeRRset(Name name, int type) { ++ Object types = data.get(name); ++ if (types == null) { ++ return; ++ } ++ if (types instanceof List) { ++ List list = (List) types; ++ for (int i = 0; i < list.size(); i++) { ++ RRset set = (RRset) list.get(i); ++ if (set.getType() == type) { ++ list.remove(i); ++ if (list.size() == 0) ++ data.remove(name); ++ return; ++ } ++ } ++ } else { ++ RRset set = (RRset) types; ++ if (set.getType() != type) ++ return; ++ data.remove(name); ++ } ++} ++ ++private synchronized SetResponse ++lookup(Name name, int type) { ++ int labels; ++ int olabels; ++ int tlabels; ++ RRset rrset; ++ Name tname; ++ Object types; ++ SetResponse sr; ++ ++ if (!name.subdomain(origin)) ++ return SetResponse.ofType(SetResponse.NXDOMAIN); ++ ++ labels = name.labels(); ++ olabels = origin.labels(); ++ ++ for (tlabels = olabels; tlabels <= labels; tlabels++) { ++ boolean isOrigin = (tlabels == olabels); ++ boolean isExact = (tlabels == labels); ++ ++ if (isOrigin) ++ tname = origin; ++ else if (isExact) ++ tname = name; ++ else ++ tname = new Name(name, labels - tlabels); ++ ++ types = exactName(tname); ++ if (types == null) ++ continue; ++ ++ /* If this is a delegation, return that. */ ++ if (!isOrigin) { ++ RRset ns = oneRRset(types, Type.NS); ++ if (ns != null) ++ return new SetResponse(SetResponse.DELEGATION, ++ ns); ++ } ++ ++ /* If this is an ANY lookup, return everything. */ ++ if (isExact && type == Type.ANY) { ++ sr = new SetResponse(SetResponse.SUCCESSFUL); ++ RRset [] sets = allRRsets(types); ++ for (int i = 0; i < sets.length; i++) ++ sr.addRRset(sets[i]); ++ return sr; ++ } ++ ++ /* ++ * If this is the name, look for the actual type or a CNAME. ++ * Otherwise, look for a DNAME. ++ */ ++ if (isExact) { ++ rrset = oneRRset(types, type); ++ if (rrset != null) { ++ sr = new SetResponse(SetResponse.SUCCESSFUL); ++ sr.addRRset(rrset); ++ return sr; ++ } ++ rrset = oneRRset(types, Type.CNAME); ++ if (rrset != null) ++ return new SetResponse(SetResponse.CNAME, ++ rrset); ++ } else { ++ rrset = oneRRset(types, Type.DNAME); ++ if (rrset != null) ++ return new SetResponse(SetResponse.DNAME, ++ rrset); ++ } ++ ++ /* We found the name, but not the type. */ ++ if (isExact) ++ return SetResponse.ofType(SetResponse.NXRRSET); ++ } ++ ++ if (hasWild) { ++ for (int i = 0; i < labels - olabels; i++) { ++ tname = name.wild(i + 1); ++ ++ types = exactName(tname); ++ if (types == null) ++ continue; ++ ++ rrset = oneRRset(types, type); ++ if (rrset != null) { ++ sr = new SetResponse(SetResponse.SUCCESSFUL); ++ sr.addRRset(rrset); ++ return sr; ++ } ++ } ++ } ++ ++ return SetResponse.ofType(SetResponse.NXDOMAIN); ++} ++ ++/** ++ * Looks up Records in the Zone. This follows CNAMEs and wildcards. ++ * @param name The name to look up ++ * @param type The type to look up ++ * @return A SetResponse object ++ * @see SetResponse ++ */ ++public SetResponse ++findRecords(Name name, int type) { ++ return lookup(name, type); ++} ++ ++/** ++ * Looks up Records in the zone, finding exact matches only. ++ * @param name The name to look up ++ * @param type The type to look up ++ * @return The matching RRset ++ * @see RRset ++ */ ++public RRset ++findExactMatch(Name name, int type) { ++ Object types = exactName(name); ++ if (types == null) ++ return null; ++ return oneRRset(types, type); ++} ++ ++/** ++ * Adds an RRset to the Zone ++ * @param rrset The RRset to be added ++ * @see RRset ++ */ ++public void ++addRRset(RRset rrset) { ++ Name name = rrset.getName(); ++ addRRset(name, rrset); ++} ++ ++/** ++ * Adds a Record to the Zone ++ * @param r The record to be added ++ * @see Record ++ */ ++public void ++addRecord(Record r) { ++ Name name = r.getName(); ++ int rtype = r.getRRsetType(); ++ synchronized (this) { ++ RRset rrset = findRRset(name, rtype); ++ if (rrset == null) { ++ rrset = new RRset(r); ++ addRRset(name, rrset); ++ } else { ++ rrset.addRR(r); ++ } ++ } ++} ++ ++/** ++ * Removes a record from the Zone ++ * @param r The record to be removed ++ * @see Record ++ */ ++public void ++removeRecord(Record r) { ++ Name name = r.getName(); ++ int rtype = r.getRRsetType(); ++ synchronized (this) { ++ RRset rrset = findRRset(name, rtype); ++ if (rrset == null) ++ return; ++ if (rrset.size() == 1 && rrset.first().equals(r)) ++ removeRRset(name, rtype); ++ else ++ rrset.deleteRR(r); ++ } ++} ++ ++/** ++ * Returns an Iterator over the RRsets in the zone. ++ */ ++public Iterator ++iterator() { ++ return new ZoneIterator(false); ++} ++ ++/** ++ * Returns an Iterator over the RRsets in the zone that can be used to ++ * construct an AXFR response. This is identical to {@link #iterator} except ++ * that the SOA is returned at the end as well as the beginning. ++ */ ++public Iterator ++AXFR() { ++ return new ZoneIterator(true); ++} ++ ++private void ++nodeToString(StringBuffer sb, Object node) { ++ RRset [] sets = allRRsets(node); ++ for (int i = 0; i < sets.length; i++) { ++ RRset rrset = sets[i]; ++ Iterator it = rrset.rrs(); ++ while (it.hasNext()) ++ sb.append(it.next() + "\n"); ++ it = rrset.sigs(); ++ while (it.hasNext()) ++ sb.append(it.next() + "\n"); ++ } ++} ++ ++/** ++ * Returns the contents of the Zone in master file format. ++ */ ++public synchronized String ++toMasterFile() { ++ Iterator zentries = data.entrySet().iterator(); ++ StringBuffer sb = new StringBuffer(); ++ nodeToString(sb, originNode); ++ while (zentries.hasNext()) { ++ Map.Entry entry = (Map.Entry) zentries.next(); ++ if (!origin.equals(entry.getKey())) ++ nodeToString(sb, entry.getValue()); ++ } ++ return sb.toString(); ++} ++ ++/** ++ * Returns the contents of the Zone as a string (in master file format). ++ */ ++public String ++toString() { ++ return toMasterFile(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ZoneTransferException.java b/external/asmack/build/src/trunk/org/xbill/DNS/ZoneTransferException.java +new file mode 100644 +index 0000000..3ba487b +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/ZoneTransferException.java +@@ -0,0 +1,23 @@ ++// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS; ++ ++/** ++ * An exception thrown when a zone transfer fails. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class ZoneTransferException extends Exception { ++ ++public ++ZoneTransferException() { ++ super(); ++} ++ ++public ++ZoneTransferException(String s) { ++ super(s); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ZoneTransferIn.java b/external/asmack/build/src/trunk/org/xbill/DNS/ZoneTransferIn.java +new file mode 100644 +index 0000000..8a19992 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/ZoneTransferIn.java +@@ -0,0 +1,680 @@ ++// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) ++// Parts of this are derived from lib/dns/xfrin.c from BIND 9; its copyright ++// notice follows. ++ ++/* ++ * Copyright (C) 1999-2001 Internet Software Consortium. ++ * ++ * Permission to use, copy, modify, and distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM ++ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ++ * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, ++ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING ++ * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, ++ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION ++ * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++package org.xbill.DNS; ++ ++import java.io.*; ++import java.net.*; ++import java.util.*; ++ ++/** ++ * An incoming DNS Zone Transfer. To use this class, first initialize an ++ * object, then call the run() method. If run() doesn't throw an exception ++ * the result will either be an IXFR-style response, an AXFR-style response, ++ * or an indication that the zone is up to date. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class ZoneTransferIn { ++ ++private static final int INITIALSOA = 0; ++private static final int FIRSTDATA = 1; ++private static final int IXFR_DELSOA = 2; ++private static final int IXFR_DEL = 3; ++private static final int IXFR_ADDSOA = 4; ++private static final int IXFR_ADD = 5; ++private static final int AXFR = 6; ++private static final int END = 7; ++ ++private Name zname; ++private int qtype; ++private int dclass; ++private long ixfr_serial; ++private boolean want_fallback; ++private ZoneTransferHandler handler; ++ ++private SocketAddress localAddress; ++private SocketAddress address; ++private TCPClient client; ++private TSIG tsig; ++private TSIG.StreamVerifier verifier; ++private long timeout = 900 * 1000; ++ ++private int state; ++private long end_serial; ++private long current_serial; ++private Record initialsoa; ++ ++private int rtype; ++ ++public static class Delta { ++ /** ++ * All changes between two versions of a zone in an IXFR response. ++ */ ++ ++ /** The starting serial number of this delta. */ ++ public long start; ++ ++ /** The ending serial number of this delta. */ ++ public long end; ++ ++ /** A list of records added between the start and end versions */ ++ public List adds; ++ ++ /** A list of records deleted between the start and end versions */ ++ public List deletes; ++ ++ private ++ Delta() { ++ adds = new ArrayList(); ++ deletes = new ArrayList(); ++ } ++} ++ ++public static interface ZoneTransferHandler { ++ /** ++ * Handles a Zone Transfer. ++ */ ++ ++ /** ++ * Called when an AXFR transfer begins. ++ */ ++ public void startAXFR() throws ZoneTransferException; ++ ++ /** ++ * Called when an IXFR transfer begins. ++ */ ++ public void startIXFR() throws ZoneTransferException; ++ ++ /** ++ * Called when a series of IXFR deletions begins. ++ * @param soa The starting SOA. ++ */ ++ public void startIXFRDeletes(Record soa) throws ZoneTransferException; ++ ++ /** ++ * Called when a series of IXFR adds begins. ++ * @param soa The starting SOA. ++ */ ++ public void startIXFRAdds(Record soa) throws ZoneTransferException; ++ ++ /** ++ * Called for each content record in an AXFR. ++ * @param r The DNS record. ++ */ ++ public void handleRecord(Record r) throws ZoneTransferException; ++}; ++ ++private static class BasicHandler implements ZoneTransferHandler { ++ private List axfr; ++ private List ixfr; ++ ++ public void startAXFR() { ++ axfr = new ArrayList(); ++ } ++ ++ public void startIXFR() { ++ ixfr = new ArrayList(); ++ } ++ ++ public void startIXFRDeletes(Record soa) { ++ Delta delta = new Delta(); ++ delta.deletes.add(soa); ++ delta.start = getSOASerial(soa); ++ ixfr.add(delta); ++ } ++ ++ public void startIXFRAdds(Record soa) { ++ Delta delta = (Delta) ixfr.get(ixfr.size() - 1); ++ delta.adds.add(soa); ++ delta.end = getSOASerial(soa); ++ } ++ ++ public void handleRecord(Record r) { ++ List list; ++ if (ixfr != null) { ++ Delta delta = (Delta) ixfr.get(ixfr.size() - 1); ++ if (delta.adds.size() > 0) ++ list = delta.adds; ++ else ++ list = delta.deletes; ++ } else ++ list = axfr; ++ list.add(r); ++ } ++}; ++ ++private ++ZoneTransferIn() {} ++ ++private ++ZoneTransferIn(Name zone, int xfrtype, long serial, boolean fallback, ++ SocketAddress address, TSIG key) ++{ ++ this.address = address; ++ this.tsig = key; ++ if (zone.isAbsolute()) ++ zname = zone; ++ else { ++ try { ++ zname = Name.concatenate(zone, Name.root); ++ } ++ catch (NameTooLongException e) { ++ throw new IllegalArgumentException("ZoneTransferIn: " + ++ "name too long"); ++ } ++ } ++ qtype = xfrtype; ++ dclass = DClass.IN; ++ ixfr_serial = serial; ++ want_fallback = fallback; ++ state = INITIALSOA; ++} ++ ++/** ++ * Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer). ++ * @param zone The zone to transfer. ++ * @param address The host/port from which to transfer the zone. ++ * @param key The TSIG key used to authenticate the transfer, or null. ++ * @return The ZoneTransferIn object. ++ * @throws UnknownHostException The host does not exist. ++ */ ++public static ZoneTransferIn ++newAXFR(Name zone, SocketAddress address, TSIG key) { ++ return new ZoneTransferIn(zone, Type.AXFR, 0, false, address, key); ++} ++ ++/** ++ * Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer). ++ * @param zone The zone to transfer. ++ * @param host The host from which to transfer the zone. ++ * @param port The port to connect to on the server, or 0 for the default. ++ * @param key The TSIG key used to authenticate the transfer, or null. ++ * @return The ZoneTransferIn object. ++ * @throws UnknownHostException The host does not exist. ++ */ ++public static ZoneTransferIn ++newAXFR(Name zone, String host, int port, TSIG key) ++throws UnknownHostException ++{ ++ if (port == 0) ++ port = SimpleResolver.DEFAULT_PORT; ++ return newAXFR(zone, new InetSocketAddress(host, port), key); ++} ++ ++/** ++ * Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer). ++ * @param zone The zone to transfer. ++ * @param host The host from which to transfer the zone. ++ * @param key The TSIG key used to authenticate the transfer, or null. ++ * @return The ZoneTransferIn object. ++ * @throws UnknownHostException The host does not exist. ++ */ ++public static ZoneTransferIn ++newAXFR(Name zone, String host, TSIG key) ++throws UnknownHostException ++{ ++ return newAXFR(zone, host, 0, key); ++} ++ ++/** ++ * Instantiates a ZoneTransferIn object to do an IXFR (incremental zone ++ * transfer). ++ * @param zone The zone to transfer. ++ * @param serial The existing serial number. ++ * @param fallback If true, fall back to AXFR if IXFR is not supported. ++ * @param address The host/port from which to transfer the zone. ++ * @param key The TSIG key used to authenticate the transfer, or null. ++ * @return The ZoneTransferIn object. ++ * @throws UnknownHostException The host does not exist. ++ */ ++public static ZoneTransferIn ++newIXFR(Name zone, long serial, boolean fallback, SocketAddress address, ++ TSIG key) ++{ ++ return new ZoneTransferIn(zone, Type.IXFR, serial, fallback, address, ++ key); ++} ++ ++/** ++ * Instantiates a ZoneTransferIn object to do an IXFR (incremental zone ++ * transfer). ++ * @param zone The zone to transfer. ++ * @param serial The existing serial number. ++ * @param fallback If true, fall back to AXFR if IXFR is not supported. ++ * @param host The host from which to transfer the zone. ++ * @param port The port to connect to on the server, or 0 for the default. ++ * @param key The TSIG key used to authenticate the transfer, or null. ++ * @return The ZoneTransferIn object. ++ * @throws UnknownHostException The host does not exist. ++ */ ++public static ZoneTransferIn ++newIXFR(Name zone, long serial, boolean fallback, String host, int port, ++ TSIG key) ++throws UnknownHostException ++{ ++ if (port == 0) ++ port = SimpleResolver.DEFAULT_PORT; ++ return newIXFR(zone, serial, fallback, ++ new InetSocketAddress(host, port), key); ++} ++ ++/** ++ * Instantiates a ZoneTransferIn object to do an IXFR (incremental zone ++ * transfer). ++ * @param zone The zone to transfer. ++ * @param serial The existing serial number. ++ * @param fallback If true, fall back to AXFR if IXFR is not supported. ++ * @param host The host from which to transfer the zone. ++ * @param key The TSIG key used to authenticate the transfer, or null. ++ * @return The ZoneTransferIn object. ++ * @throws UnknownHostException The host does not exist. ++ */ ++public static ZoneTransferIn ++newIXFR(Name zone, long serial, boolean fallback, String host, TSIG key) ++throws UnknownHostException ++{ ++ return newIXFR(zone, serial, fallback, host, 0, key); ++} ++ ++/** ++ * Gets the name of the zone being transferred. ++ */ ++public Name ++getName() { ++ return zname; ++} ++ ++/** ++ * Gets the type of zone transfer (either AXFR or IXFR). ++ */ ++public int ++getType() { ++ return qtype; ++} ++ ++/** ++ * Sets a timeout on this zone transfer. The default is 900 seconds (15 ++ * minutes). ++ * @param secs The maximum amount of time that this zone transfer can take. ++ */ ++public void ++setTimeout(int secs) { ++ if (secs < 0) ++ throw new IllegalArgumentException("timeout cannot be " + ++ "negative"); ++ timeout = 1000L * secs; ++} ++ ++/** ++ * Sets an alternate DNS class for this zone transfer. ++ * @param dclass The class to use instead of class IN. ++ */ ++public void ++setDClass(int dclass) { ++ DClass.check(dclass); ++ this.dclass = dclass; ++} ++ ++/** ++ * Sets the local address to bind to when sending messages. ++ * @param addr The local address to send messages from. ++ */ ++public void ++setLocalAddress(SocketAddress addr) { ++ this.localAddress = addr; ++} ++ ++private void ++openConnection() throws IOException { ++ long endTime = System.currentTimeMillis() + timeout; ++ client = new TCPClient(endTime); ++ if (localAddress != null) ++ client.bind(localAddress); ++ client.connect(address); ++} ++ ++private void ++sendQuery() throws IOException { ++ Record question = Record.newRecord(zname, qtype, dclass); ++ ++ Message query = new Message(); ++ query.getHeader().setOpcode(Opcode.QUERY); ++ query.addRecord(question, Section.QUESTION); ++ if (qtype == Type.IXFR) { ++ Record soa = new SOARecord(zname, dclass, 0, Name.root, ++ Name.root, ixfr_serial, ++ 0, 0, 0, 0); ++ query.addRecord(soa, Section.AUTHORITY); ++ } ++ if (tsig != null) { ++ tsig.apply(query, null); ++ verifier = new TSIG.StreamVerifier(tsig, query.getTSIG()); ++ } ++ byte [] out = query.toWire(Message.MAXLENGTH); ++ client.send(out); ++} ++ ++private static long ++getSOASerial(Record rec) { ++ SOARecord soa = (SOARecord) rec; ++ return soa.getSerial(); ++} ++ ++private void ++logxfr(String s) { ++ if (Options.check("verbose")) ++ System.out.println(zname + ": " + s); ++} ++ ++private void ++fail(String s) throws ZoneTransferException { ++ throw new ZoneTransferException(s); ++} ++ ++private void ++fallback() throws ZoneTransferException { ++ if (!want_fallback) ++ fail("server doesn't support IXFR"); ++ ++ logxfr("falling back to AXFR"); ++ qtype = Type.AXFR; ++ state = INITIALSOA; ++} ++ ++private void ++parseRR(Record rec) throws ZoneTransferException { ++ int type = rec.getType(); ++ Delta delta; ++ ++ switch (state) { ++ case INITIALSOA: ++ if (type != Type.SOA) ++ fail("missing initial SOA"); ++ initialsoa = rec; ++ // Remember the serial number in the initial SOA; we need it ++ // to recognize the end of an IXFR. ++ end_serial = getSOASerial(rec); ++ if (qtype == Type.IXFR && ++ Serial.compare(end_serial, ixfr_serial) <= 0) ++ { ++ logxfr("up to date"); ++ state = END; ++ break; ++ } ++ state = FIRSTDATA; ++ break; ++ ++ case FIRSTDATA: ++ // If the transfer begins with 1 SOA, it's an AXFR. ++ // If it begins with 2 SOAs, it's an IXFR. ++ if (qtype == Type.IXFR && type == Type.SOA && ++ getSOASerial(rec) == ixfr_serial) ++ { ++ rtype = Type.IXFR; ++ handler.startIXFR(); ++ logxfr("got incremental response"); ++ state = IXFR_DELSOA; ++ } else { ++ rtype = Type.AXFR; ++ handler.startAXFR(); ++ handler.handleRecord(initialsoa); ++ logxfr("got nonincremental response"); ++ state = AXFR; ++ } ++ parseRR(rec); // Restart... ++ return; ++ ++ case IXFR_DELSOA: ++ handler.startIXFRDeletes(rec); ++ state = IXFR_DEL; ++ break; ++ ++ case IXFR_DEL: ++ if (type == Type.SOA) { ++ current_serial = getSOASerial(rec); ++ state = IXFR_ADDSOA; ++ parseRR(rec); // Restart... ++ return; ++ } ++ handler.handleRecord(rec); ++ break; ++ ++ case IXFR_ADDSOA: ++ handler.startIXFRAdds(rec); ++ state = IXFR_ADD; ++ break; ++ ++ case IXFR_ADD: ++ if (type == Type.SOA) { ++ long soa_serial = getSOASerial(rec); ++ if (soa_serial == end_serial) { ++ state = END; ++ break; ++ } else if (soa_serial != current_serial) { ++ fail("IXFR out of sync: expected serial " + ++ current_serial + " , got " + soa_serial); ++ } else { ++ state = IXFR_DELSOA; ++ parseRR(rec); // Restart... ++ return; ++ } ++ } ++ handler.handleRecord(rec); ++ break; ++ ++ case AXFR: ++ // Old BINDs sent cross class A records for non IN classes. ++ if (type == Type.A && rec.getDClass() != dclass) ++ break; ++ handler.handleRecord(rec); ++ if (type == Type.SOA) { ++ state = END; ++ } ++ break; ++ ++ case END: ++ fail("extra data"); ++ break; ++ ++ default: ++ fail("invalid state"); ++ break; ++ } ++} ++ ++private void ++closeConnection() { ++ try { ++ if (client != null) ++ client.cleanup(); ++ } ++ catch (IOException e) { ++ } ++} ++ ++private Message ++parseMessage(byte [] b) throws WireParseException { ++ try { ++ return new Message(b); ++ } ++ catch (IOException e) { ++ if (e instanceof WireParseException) ++ throw (WireParseException) e; ++ throw new WireParseException("Error parsing message"); ++ } ++} ++ ++private void ++doxfr() throws IOException, ZoneTransferException { ++ sendQuery(); ++ while (state != END) { ++ byte [] in = client.recv(); ++ Message response = parseMessage(in); ++ if (response.getHeader().getRcode() == Rcode.NOERROR && ++ verifier != null) ++ { ++ TSIGRecord tsigrec = response.getTSIG(); ++ ++ int error = verifier.verify(response, in); ++ if (error != Rcode.NOERROR) ++ fail("TSIG failure"); ++ } ++ ++ Record [] answers = response.getSectionArray(Section.ANSWER); ++ ++ if (state == INITIALSOA) { ++ int rcode = response.getRcode(); ++ if (rcode != Rcode.NOERROR) { ++ if (qtype == Type.IXFR && ++ rcode == Rcode.NOTIMP) ++ { ++ fallback(); ++ doxfr(); ++ return; ++ } ++ fail(Rcode.string(rcode)); ++ } ++ ++ Record question = response.getQuestion(); ++ if (question != null && question.getType() != qtype) { ++ fail("invalid question section"); ++ } ++ ++ if (answers.length == 0 && qtype == Type.IXFR) { ++ fallback(); ++ doxfr(); ++ return; ++ } ++ } ++ ++ for (int i = 0; i < answers.length; i++) { ++ parseRR(answers[i]); ++ } ++ ++ if (state == END && verifier != null && ++ !response.isVerified()) ++ fail("last message must be signed"); ++ } ++} ++ ++/** ++ * Does the zone transfer. ++ * @param handler The callback object that handles the zone transfer data. ++ * @throws IOException The zone transfer failed to due an IO problem. ++ * @throws ZoneTransferException The zone transfer failed to due a problem ++ * with the zone transfer itself. ++ */ ++public void ++run(ZoneTransferHandler handler) throws IOException, ZoneTransferException { ++ this.handler = handler; ++ try { ++ openConnection(); ++ doxfr(); ++ } ++ finally { ++ closeConnection(); ++ } ++} ++ ++/** ++ * Does the zone transfer. ++ * @return A list, which is either an AXFR-style response (List of Records), ++ * and IXFR-style response (List of Deltas), or null, which indicates that ++ * an IXFR was performed and the zone is up to date. ++ * @throws IOException The zone transfer failed to due an IO problem. ++ * @throws ZoneTransferException The zone transfer failed to due a problem ++ * with the zone transfer itself. ++ */ ++public List ++run() throws IOException, ZoneTransferException { ++ BasicHandler handler = new BasicHandler(); ++ run(handler); ++ if (handler.axfr != null) ++ return handler.axfr; ++ return handler.ixfr; ++} ++ ++private BasicHandler ++getBasicHandler() throws IllegalArgumentException { ++ if (handler instanceof BasicHandler) ++ return (BasicHandler) handler; ++ throw new IllegalArgumentException("ZoneTransferIn used callback " + ++ "interface"); ++} ++ ++/** ++ * Returns true if the response is an AXFR-style response (List of Records). ++ * This will be true if either an IXFR was performed, an IXFR was performed ++ * and the server provided a full zone transfer, or an IXFR failed and ++ * fallback to AXFR occurred. ++ */ ++public boolean ++isAXFR() { ++ return (rtype == Type.AXFR); ++} ++ ++/** ++ * Gets the AXFR-style response. ++ * @throws IllegalArgumentException The transfer used the callback interface, ++ * so the response was not stored. ++ */ ++public List ++getAXFR() { ++ BasicHandler handler = getBasicHandler(); ++ return handler.axfr; ++} ++ ++/** ++ * Returns true if the response is an IXFR-style response (List of Deltas). ++ * This will be true only if an IXFR was performed and the server provided ++ * an incremental zone transfer. ++ */ ++public boolean ++isIXFR() { ++ return (rtype == Type.IXFR); ++} ++ ++/** ++ * Gets the IXFR-style response. ++ * @throws IllegalArgumentException The transfer used the callback interface, ++ * so the response was not stored. ++ */ ++public List ++getIXFR() { ++ BasicHandler handler = getBasicHandler(); ++ return handler.ixfr; ++} ++ ++/** ++ * Returns true if the response indicates that the zone is up to date. ++ * This will be true only if an IXFR was performed. ++ * @throws IllegalArgumentException The transfer used the callback interface, ++ * so the response was not stored. ++ */ ++public boolean ++isCurrent() { ++ BasicHandler handler = getBasicHandler(); ++ return (handler.axfr == null && handler.ixfr == null); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/spi/DNSJavaNameService.java b/external/asmack/build/src/trunk/org/xbill/DNS/spi/DNSJavaNameService.java +new file mode 100644 +index 0000000..14d8adb +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/spi/DNSJavaNameService.java +@@ -0,0 +1,176 @@ ++// Copyright (c) 2005 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS.spi; ++ ++import java.lang.reflect.InvocationHandler; ++import java.lang.reflect.Method; ++import java.net.InetAddress; ++import java.net.UnknownHostException; ++import java.util.StringTokenizer; ++ ++import org.xbill.DNS.AAAARecord; ++import org.xbill.DNS.ARecord; ++import org.xbill.DNS.ExtendedResolver; ++import org.xbill.DNS.Lookup; ++import org.xbill.DNS.Name; ++import org.xbill.DNS.PTRRecord; ++import org.xbill.DNS.Record; ++import org.xbill.DNS.Resolver; ++import org.xbill.DNS.ReverseMap; ++import org.xbill.DNS.TextParseException; ++import org.xbill.DNS.Type; ++ ++/** ++ * This class implements a Name Service Provider, which Java can use ++ * (starting with version 1.4), to perform DNS resolutions instead of using ++ * the standard calls. ++ *

      ++ * This Name Service Provider uses dnsjava. ++ *

      ++ * To use this provider, you must set the following system property: ++ * sun.net.spi.nameservice.provider.1=dns,dnsjava ++ * ++ * @author Brian Wellington ++ * @author Paul Cowan (pwc21@yahoo.com) ++ */ ++ ++public class DNSJavaNameService implements InvocationHandler { ++ ++private static final String nsProperty = "sun.net.spi.nameservice.nameservers"; ++private static final String domainProperty = "sun.net.spi.nameservice.domain"; ++private static final String v6Property = "java.net.preferIPv6Addresses"; ++ ++private boolean preferV6 = false; ++ ++/** ++ * Creates a DNSJavaNameService instance. ++ *

      ++ * Uses the ++ * sun.net.spi.nameservice.nameservers, ++ * sun.net.spi.nameservice.domain, and ++ * java.net.preferIPv6Addresses properties for configuration. ++ */ ++protected ++DNSJavaNameService() { ++ String nameServers = System.getProperty(nsProperty); ++ String domain = System.getProperty(domainProperty); ++ String v6 = System.getProperty(v6Property); ++ ++ if (nameServers != null) { ++ StringTokenizer st = new StringTokenizer(nameServers, ","); ++ String [] servers = new String[st.countTokens()]; ++ int n = 0; ++ while (st.hasMoreTokens()) ++ servers[n++] = st.nextToken(); ++ try { ++ Resolver res = new ExtendedResolver(servers); ++ Lookup.setDefaultResolver(res); ++ } ++ catch (UnknownHostException e) { ++ System.err.println("DNSJavaNameService: invalid " + ++ nsProperty); ++ } ++ } ++ ++ if (domain != null) { ++ try { ++ Lookup.setDefaultSearchPath(new String[] {domain}); ++ } ++ catch (TextParseException e) { ++ System.err.println("DNSJavaNameService: invalid " + ++ domainProperty); ++ } ++ } ++ ++ if (v6 != null && v6.equalsIgnoreCase("true")) ++ preferV6 = true; ++} ++ ++ ++public Object ++invoke(Object proxy, Method method, Object[] args) throws Throwable { ++ try { ++ if (method.getName().equals("getHostByAddr")) { ++ return this.getHostByAddr((byte[]) args[0]); ++ } else if (method.getName().equals("lookupAllHostAddr")) { ++ InetAddress[] addresses; ++ addresses = this.lookupAllHostAddr((String) args[0]); ++ Class returnType = method.getReturnType(); ++ if (returnType.equals(InetAddress[].class)) { ++ // method for Java >= 1.6 ++ return addresses; ++ } else if (returnType.equals(byte[][].class)) { ++ // method for Java <= 1.5 ++ int naddrs = addresses.length; ++ byte [][] byteAddresses = new byte[naddrs][]; ++ byte [] addr; ++ for (int i = 0; i < naddrs; i++) { ++ addr = addresses[i].getAddress(); ++ byteAddresses[i] = addr; ++ } ++ return byteAddresses; ++ } ++ } ++ } catch (Throwable e) { ++ System.err.println("DNSJavaNameService: Unexpected error."); ++ e.printStackTrace(); ++ throw e; ++ } ++ throw new IllegalArgumentException( ++ "Unknown function name or arguments."); ++} ++ ++/** ++ * Performs a forward DNS lookup for the host name. ++ * @param host The host name to resolve. ++ * @return All the ip addresses found for the host name. ++ */ ++public InetAddress [] ++lookupAllHostAddr(String host) throws UnknownHostException { ++ Name name = null; ++ ++ try { ++ name = new Name(host); ++ } ++ catch (TextParseException e) { ++ throw new UnknownHostException(host); ++ } ++ ++ Record [] records = null; ++ if (preferV6) ++ records = new Lookup(name, Type.AAAA).run(); ++ if (records == null) ++ records = new Lookup(name, Type.A).run(); ++ if (records == null && !preferV6) ++ records = new Lookup(name, Type.AAAA).run(); ++ if (records == null) ++ throw new UnknownHostException(host); ++ ++ InetAddress[] array = new InetAddress[records.length]; ++ for (int i = 0; i < records.length; i++) { ++ Record record = records[i]; ++ if (records[i] instanceof ARecord) { ++ ARecord a = (ARecord) records[i]; ++ array[i] = a.getAddress(); ++ } else { ++ AAAARecord aaaa = (AAAARecord) records[i]; ++ array[i] = aaaa.getAddress(); ++ } ++ } ++ return array; ++} ++ ++/** ++ * Performs a reverse DNS lookup. ++ * @param addr The ip address to lookup. ++ * @return The host name found for the ip address. ++ */ ++public String ++getHostByAddr(byte [] addr) throws UnknownHostException { ++ Name name = ReverseMap.fromAddress(InetAddress.getByAddress(addr)); ++ Record [] records = new Lookup(name, Type.PTR).run(); ++ if (records == null) ++ throw new UnknownHostException(); ++ return ((PTRRecord) records[0]).getTarget().toString(); ++} ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/spi/services/sun.net.spi.nameservice.NameServiceDescriptor b/external/asmack/build/src/trunk/org/xbill/DNS/spi/services/sun.net.spi.nameservice.NameServiceDescriptor +new file mode 100644 +index 0000000..1ca895c +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/spi/services/sun.net.spi.nameservice.NameServiceDescriptor +@@ -0,0 +1 @@ ++org.xbill.DNS.spi.DNSJavaNameServiceDescriptor +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/tests/primary.java b/external/asmack/build/src/trunk/org/xbill/DNS/tests/primary.java +new file mode 100644 +index 0000000..85455b9 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/tests/primary.java +@@ -0,0 +1,59 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS.tests; ++ ++import java.util.*; ++import org.xbill.DNS.*; ++ ++public class primary { ++ ++private static void ++usage() { ++ System.out.println("usage: primary [-t] [-a | -i] origin file"); ++ System.exit(1); ++} ++ ++public static void ++main(String [] args) throws Exception { ++ boolean time = false; ++ boolean axfr = false; ++ boolean iterator = false; ++ int arg = 0; ++ ++ if (args.length < 2) ++ usage(); ++ ++ while (args.length - arg > 2) { ++ if (args[0].equals("-t")) ++ time = true; ++ else if (args[0].equals("-a")) ++ axfr = true; ++ else if (args[0].equals("-i")) ++ iterator = true; ++ arg++; ++ } ++ ++ Name origin = Name.fromString(args[arg++], Name.root); ++ String file = args[arg++]; ++ ++ long start = System.currentTimeMillis(); ++ Zone zone = new Zone(origin, file); ++ long end = System.currentTimeMillis(); ++ if (axfr) { ++ Iterator it = zone.AXFR(); ++ while (it.hasNext()) { ++ System.out.println(it.next()); ++ } ++ } else if (iterator) { ++ Iterator it = zone.iterator(); ++ while (it.hasNext()) { ++ System.out.println(it.next()); ++ } ++ } else { ++ System.out.println(zone); ++ } ++ if (time) ++ System.out.println("; Load time: " + (end - start) + " ms"); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/tests/xfrin.java b/external/asmack/build/src/trunk/org/xbill/DNS/tests/xfrin.java +new file mode 100644 +index 0000000..066c70e +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/tests/xfrin.java +@@ -0,0 +1,109 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS.tests; ++ ++import java.util.*; ++import org.xbill.DNS.*; ++ ++public class xfrin { ++ ++private static void ++usage(String s) { ++ System.out.println("Error: " + s); ++ System.out.println("usage: xfrin [-i serial] [-k keyname/secret] " + ++ "[-s server] [-p port] [-f] zone"); ++ System.exit(1); ++} ++ ++public static void ++main(String [] args) throws Exception { ++ ZoneTransferIn xfrin; ++ TSIG key = null; ++ int ixfr_serial = -1; ++ String server = null; ++ int port = SimpleResolver.DEFAULT_PORT; ++ boolean fallback = false; ++ Name zname; ++ ++ int arg = 0; ++ while (arg < args.length) { ++ if (args[arg].equals("-i")) { ++ ixfr_serial = Integer.parseInt(args[++arg]); ++ if (ixfr_serial < 0) ++ usage("invalid serial number"); ++ } else if (args[arg].equals("-k")) { ++ String s = args[++arg]; ++ int index = s.indexOf('/'); ++ if (index < 0) ++ usage("invalid key"); ++ key = new TSIG(s.substring(0, index), ++ s.substring(index+1)); ++ } else if (args[arg].equals("-s")) { ++ server = args[++arg]; ++ } else if (args[arg].equals("-p")) { ++ port = Integer.parseInt(args[++arg]); ++ if (port < 0 || port > 0xFFFF) ++ usage("invalid port"); ++ } else if (args[arg].equals("-f")) { ++ fallback = true; ++ } else if (args[arg].startsWith("-")) { ++ usage("invalid option"); ++ } else { ++ break; ++ } ++ arg++; ++ } ++ if (arg >= args.length) ++ usage("no zone name specified"); ++ zname = Name.fromString(args[arg]); ++ ++ if (server == null) { ++ Lookup l = new Lookup(zname, Type.NS); ++ Record [] ns = l.run(); ++ if (ns == null) { ++ System.out.println("failed to look up NS record: " + ++ l.getErrorString()); ++ System.exit(1); ++ } ++ server = ns[0].rdataToString(); ++ System.out.println("sending to server '" + server + "'"); ++ } ++ ++ if (ixfr_serial >= 0) ++ xfrin = ZoneTransferIn.newIXFR(zname, ixfr_serial, fallback, ++ server, port, key); ++ else ++ xfrin = ZoneTransferIn.newAXFR(zname, server, port, key); ++ ++ List response = xfrin.run(); ++ if (xfrin.isAXFR()) { ++ if (ixfr_serial >= 0) ++ System.out.println("AXFR-like IXFR response"); ++ else ++ System.out.println("AXFR response"); ++ Iterator it = response.iterator(); ++ while (it.hasNext()) ++ System.out.println(it.next()); ++ } else if (xfrin.isIXFR()) { ++ System.out.println("IXFR response"); ++ Iterator it = response.iterator(); ++ while (it.hasNext()) { ++ ZoneTransferIn.Delta delta; ++ delta = (ZoneTransferIn.Delta) it.next(); ++ System.out.println("delta from " + delta.start + ++ " to " + delta.end); ++ System.out.println("deletes"); ++ Iterator it2 = delta.deletes.iterator(); ++ while (it2.hasNext()) ++ System.out.println(it2.next()); ++ System.out.println("adds"); ++ it2 = delta.adds.iterator(); ++ while (it2.hasNext()) ++ System.out.println(it2.next()); ++ } ++ } else if (xfrin.isCurrent()) { ++ System.out.println("up to date"); ++ } ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/utils/HMAC.java b/external/asmack/build/src/trunk/org/xbill/DNS/utils/HMAC.java +new file mode 100644 +index 0000000..5eb5afd +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/utils/HMAC.java +@@ -0,0 +1,182 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS.utils; ++ ++import java.util.Arrays; ++import java.security.*; ++ ++/** ++ * An implementation of the HMAC message authentication code. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class HMAC { ++ ++private MessageDigest digest; ++private int blockLength; ++ ++private byte [] ipad, opad; ++ ++private static final byte IPAD = 0x36; ++private static final byte OPAD = 0x5c; ++ ++private void ++init(byte [] key) { ++ int i; ++ ++ if (key.length > blockLength) { ++ key = digest.digest(key); ++ digest.reset(); ++ } ++ ipad = new byte[blockLength]; ++ opad = new byte[blockLength]; ++ for (i = 0; i < key.length; i++) { ++ ipad[i] = (byte) (key[i] ^ IPAD); ++ opad[i] = (byte) (key[i] ^ OPAD); ++ } ++ for (; i < blockLength; i++) { ++ ipad[i] = IPAD; ++ opad[i] = OPAD; ++ } ++ digest.update(ipad); ++} ++ ++/** ++ * Creates a new HMAC instance ++ * @param digest The message digest object. ++ * @param blockLength The block length of the message digest. ++ * @param key The secret key ++ */ ++public ++HMAC(MessageDigest digest, int blockLength, byte [] key) { ++ digest.reset(); ++ this.digest = digest; ++ this.blockLength = blockLength; ++ init(key); ++} ++ ++/** ++ * Creates a new HMAC instance ++ * @param digestName The name of the message digest function. ++ * @param blockLength The block length of the message digest. ++ * @param key The secret key. ++ */ ++public ++HMAC(String digestName, int blockLength, byte [] key) { ++ try { ++ digest = MessageDigest.getInstance(digestName); ++ } catch (NoSuchAlgorithmException e) { ++ throw new IllegalArgumentException("unknown digest algorithm " ++ + digestName); ++ } ++ this.blockLength = blockLength; ++ init(key); ++} ++ ++/** ++ * Creates a new HMAC instance ++ * @param digest The message digest object. ++ * @param key The secret key ++ * @deprecated won't work with digests using a padding length other than 64; ++ * use {@code HMAC(MessageDigest digest, int blockLength, ++ * byte [] key)} instead. ++ * @see HMAC#HMAC(MessageDigest digest, int blockLength, byte [] key) ++ */ ++public ++HMAC(MessageDigest digest, byte [] key) { ++ this(digest, 64, key); ++} ++ ++/** ++ * Creates a new HMAC instance ++ * @param digestName The name of the message digest function. ++ * @param key The secret key. ++ * @deprecated won't work with digests using a padding length other than 64; ++ * use {@code HMAC(String digestName, int blockLength, byte [] key)} ++ * instead ++ * @see HMAC#HMAC(String digestName, int blockLength, byte [] key) ++ */ ++public ++HMAC(String digestName, byte [] key) { ++ this(digestName, 64, key); ++} ++ ++/** ++ * Adds data to the current hash ++ * @param b The data ++ * @param offset The index at which to start adding to the hash ++ * @param length The number of bytes to hash ++ */ ++public void ++update(byte [] b, int offset, int length) { ++ digest.update(b, offset, length); ++} ++ ++/** ++ * Adds data to the current hash ++ * @param b The data ++ */ ++public void ++update(byte [] b) { ++ digest.update(b); ++} ++ ++/** ++ * Signs the data (computes the secure hash) ++ * @return An array with the signature ++ */ ++public byte [] ++sign() { ++ byte [] output = digest.digest(); ++ digest.reset(); ++ digest.update(opad); ++ return digest.digest(output); ++} ++ ++/** ++ * Verifies the data (computes the secure hash and compares it to the input) ++ * @param signature The signature to compare against ++ * @return true if the signature matches, false otherwise ++ */ ++public boolean ++verify(byte [] signature) { ++ return verify(signature, false); ++} ++ ++/** ++ * Verifies the data (computes the secure hash and compares it to the input) ++ * @param signature The signature to compare against ++ * @param truncation_ok If true, the signature may be truncated; only the ++ * number of bytes in the provided signature are compared. ++ * @return true if the signature matches, false otherwise ++ */ ++public boolean ++verify(byte [] signature, boolean truncation_ok) { ++ byte [] expected = sign(); ++ if (truncation_ok && signature.length < expected.length) { ++ byte [] truncated = new byte[signature.length]; ++ System.arraycopy(expected, 0, truncated, 0, truncated.length); ++ expected = truncated; ++ } ++ return Arrays.equals(signature, expected); ++} ++ ++/** ++ * Resets the HMAC object for further use ++ */ ++public void ++clear() { ++ digest.reset(); ++ digest.update(ipad); ++} ++ ++/** ++ * Returns the length of the digest. ++ */ ++public int ++digestLength() { ++ return digest.getDigestLength(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/utils/base16.java b/external/asmack/build/src/trunk/org/xbill/DNS/utils/base16.java +new file mode 100644 +index 0000000..58024e6 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/utils/base16.java +@@ -0,0 +1,73 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS.utils; ++ ++import java.io.*; ++ ++/** ++ * Routines for converting between Strings of hex-encoded data and arrays of ++ * binary data. This is not actually used by DNS. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class base16 { ++ ++private static final String Base16 = "0123456789ABCDEF"; ++ ++private ++base16() {} ++ ++/** ++ * Convert binary data to a hex-encoded String ++ * @param b An array containing binary data ++ * @return A String containing the encoded data ++ */ ++public static String ++toString(byte [] b) { ++ ByteArrayOutputStream os = new ByteArrayOutputStream(); ++ ++ for (int i = 0; i < b.length; i++) { ++ short value = (short) (b[i] & 0xFF); ++ byte high = (byte) (value >> 4); ++ byte low = (byte) (value & 0xF); ++ os.write(Base16.charAt(high)); ++ os.write(Base16.charAt(low)); ++ } ++ return new String(os.toByteArray()); ++} ++ ++/** ++ * Convert a hex-encoded String to binary data ++ * @param str A String containing the encoded data ++ * @return An array containing the binary data, or null if the string is invalid ++ */ ++public static byte [] ++fromString(String str) { ++ ByteArrayOutputStream bs = new ByteArrayOutputStream(); ++ byte [] raw = str.getBytes(); ++ for (int i = 0; i < raw.length; i++) { ++ if (!Character.isWhitespace((char)raw[i])) ++ bs.write(raw[i]); ++ } ++ byte [] in = bs.toByteArray(); ++ if (in.length % 2 != 0) { ++ return null; ++ } ++ ++ bs.reset(); ++ DataOutputStream ds = new DataOutputStream(bs); ++ ++ for (int i = 0; i < in.length; i += 2) { ++ byte high = (byte) Base16.indexOf(Character.toUpperCase((char)in[i])); ++ byte low = (byte) Base16.indexOf(Character.toUpperCase((char)in[i+1])); ++ try { ++ ds.writeByte((high << 4) + low); ++ } ++ catch (IOException e) { ++ } ++ } ++ return bs.toByteArray(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/utils/base32.java b/external/asmack/build/src/trunk/org/xbill/DNS/utils/base32.java +new file mode 100644 +index 0000000..a2f26ea +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/utils/base32.java +@@ -0,0 +1,213 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS.utils; ++ ++import java.io.*; ++ ++/** ++ * Routines for converting between Strings of base32-encoded data and arrays ++ * of binary data. This currently supports the base32 and base32hex alphabets ++ * specified in RFC 4648, sections 6 and 7. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class base32 { ++ ++public static class Alphabet { ++ private Alphabet() {} ++ ++ public static final String BASE32 = ++ "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="; ++ public static final String BASE32HEX = ++ "0123456789ABCDEFGHIJKLMNOPQRSTUV="; ++}; ++ ++private String alphabet; ++private boolean padding, lowercase; ++ ++/** ++ * Creates an object that can be used to do base32 conversions. ++ * @param alphabet Which alphabet should be used ++ * @param padding Whether padding should be used ++ * @param lowercase Whether lowercase characters should be used. ++ * default parameters (The standard base32 alphabet, no padding, uppercase) ++ */ ++public ++base32(String alphabet, boolean padding, boolean lowercase) { ++ this.alphabet = alphabet; ++ this.padding = padding; ++ this.lowercase = lowercase; ++} ++ ++static private int ++blockLenToPadding(int blocklen) { ++ switch (blocklen) { ++ case 1: ++ return 6; ++ case 2: ++ return 4; ++ case 3: ++ return 3; ++ case 4: ++ return 1; ++ case 5: ++ return 0; ++ default: ++ return -1; ++ } ++} ++ ++static private int ++paddingToBlockLen(int padlen) { ++ switch (padlen) { ++ case 6: ++ return 1; ++ case 4: ++ return 2; ++ case 3: ++ return 3; ++ case 1: ++ return 4; ++ case 0: ++ return 5; ++ default : ++ return -1; ++ } ++} ++ ++/** ++ * Convert binary data to a base32-encoded String ++ * ++ * @param b An array containing binary data ++ * @return A String containing the encoded data ++ */ ++public String ++toString(byte [] b) { ++ ByteArrayOutputStream os = new ByteArrayOutputStream(); ++ ++ for (int i = 0; i < (b.length + 4) / 5; i++) { ++ short s[] = new short[5]; ++ int t[] = new int[8]; ++ ++ int blocklen = 5; ++ for (int j = 0; j < 5; j++) { ++ if ((i * 5 + j) < b.length) ++ s[j] = (short) (b[i * 5 + j] & 0xFF); ++ else { ++ s[j] = 0; ++ blocklen--; ++ } ++ } ++ int padlen = blockLenToPadding(blocklen); ++ ++ // convert the 5 byte block into 8 characters (values 0-31). ++ ++ // upper 5 bits from first byte ++ t[0] = (byte) ((s[0] >> 3) & 0x1F); ++ // lower 3 bits from 1st byte, upper 2 bits from 2nd. ++ t[1] = (byte) (((s[0] & 0x07) << 2) | ((s[1] >> 6) & 0x03)); ++ // bits 5-1 from 2nd. ++ t[2] = (byte) ((s[1] >> 1) & 0x1F); ++ // lower 1 bit from 2nd, upper 4 from 3rd ++ t[3] = (byte) (((s[1] & 0x01) << 4) | ((s[2] >> 4) & 0x0F)); ++ // lower 4 from 3rd, upper 1 from 4th. ++ t[4] = (byte) (((s[2] & 0x0F) << 1) | ((s[3] >> 7) & 0x01)); ++ // bits 6-2 from 4th ++ t[5] = (byte) ((s[3] >> 2) & 0x1F); ++ // lower 2 from 4th, upper 3 from 5th; ++ t[6] = (byte) (((s[3] & 0x03) << 3) | ((s[4] >> 5) & 0x07)); ++ // lower 5 from 5th; ++ t[7] = (byte) (s[4] & 0x1F); ++ ++ // write out the actual characters. ++ for (int j = 0; j < t.length - padlen; j++) { ++ char c = alphabet.charAt(t[j]); ++ if (lowercase) ++ c = Character.toLowerCase(c); ++ os.write(c); ++ } ++ ++ // write out the padding (if any) ++ if (padding) { ++ for (int j = t.length - padlen; j < t.length; j++) ++ os.write('='); ++ } ++ } ++ ++ return new String(os.toByteArray()); ++} ++ ++/** ++ * Convert a base32-encoded String to binary data ++ * ++ * @param str A String containing the encoded data ++ * @return An array containing the binary data, or null if the string is invalid ++ */ ++public byte[] ++fromString(String str) { ++ ByteArrayOutputStream bs = new ByteArrayOutputStream(); ++ byte [] raw = str.getBytes(); ++ for (int i = 0; i < raw.length; i++) ++ { ++ char c = (char) raw[i]; ++ if (!Character.isWhitespace(c)) { ++ c = Character.toUpperCase(c); ++ bs.write((byte) c); ++ } ++ } ++ ++ if (padding) { ++ if (bs.size() % 8 != 0) ++ return null; ++ } else { ++ while (bs.size() % 8 != 0) ++ bs.write('='); ++ } ++ ++ byte [] in = bs.toByteArray(); ++ ++ bs.reset(); ++ DataOutputStream ds = new DataOutputStream(bs); ++ ++ for (int i = 0; i < in.length / 8; i++) { ++ short[] s = new short[8]; ++ int[] t = new int[5]; ++ ++ int padlen = 8; ++ for (int j = 0; j < 8; j++) { ++ char c = (char) in[i * 8 + j]; ++ if (c == '=') ++ break; ++ s[j] = (short) alphabet.indexOf(in[i * 8 + j]); ++ if (s[j] < 0) ++ return null; ++ padlen--; ++ } ++ int blocklen = paddingToBlockLen(padlen); ++ if (blocklen < 0) ++ return null; ++ ++ // all 5 bits of 1st, high 3 (of 5) of 2nd ++ t[0] = (s[0] << 3) | s[1] >> 2; ++ // lower 2 of 2nd, all 5 of 3rd, high 1 of 4th ++ t[1] = ((s[1] & 0x03) << 6) | (s[2] << 1) | (s[3] >> 4); ++ // lower 4 of 4th, high 4 of 5th ++ t[2] = ((s[3] & 0x0F) << 4) | ((s[4] >> 1) & 0x0F); ++ // lower 1 of 5th, all 5 of 6th, high 2 of 7th ++ t[3] = (s[4] << 7) | (s[5] << 2) | (s[6] >> 3); ++ // lower 3 of 7th, all of 8th ++ t[4] = ((s[6] & 0x07) << 5) | s[7]; ++ ++ try { ++ for (int j = 0; j < blocklen; j++) ++ ds.writeByte((byte) (t[j] & 0xFF)); ++ } ++ catch (IOException e) { ++ } ++ } ++ ++ return bs.toByteArray(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/utils/base64.java b/external/asmack/build/src/trunk/org/xbill/DNS/utils/base64.java +new file mode 100644 +index 0000000..54567cf +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/utils/base64.java +@@ -0,0 +1,145 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS.utils; ++ ++import java.io.*; ++ ++/** ++ * Routines for converting between Strings of base64-encoded data and arrays of ++ * binary data. ++ * ++ * @author Brian Wellington ++ */ ++ ++public class base64 { ++ ++private static final String Base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; ++ ++private ++base64() {} ++ ++/** ++ * Convert binary data to a base64-encoded String ++ * @param b An array containing binary data ++ * @return A String containing the encoded data ++ */ ++public static String ++toString(byte [] b) { ++ ByteArrayOutputStream os = new ByteArrayOutputStream(); ++ ++ for (int i = 0; i < (b.length + 2) / 3; i++) { ++ short [] s = new short[3]; ++ short [] t = new short[4]; ++ for (int j = 0; j < 3; j++) { ++ if ((i * 3 + j) < b.length) ++ s[j] = (short) (b[i*3+j] & 0xFF); ++ else ++ s[j] = -1; ++ } ++ ++ t[0] = (short) (s[0] >> 2); ++ if (s[1] == -1) ++ t[1] = (short) (((s[0] & 0x3) << 4)); ++ else ++ t[1] = (short) (((s[0] & 0x3) << 4) + (s[1] >> 4)); ++ if (s[1] == -1) ++ t[2] = t[3] = 64; ++ else if (s[2] == -1) { ++ t[2] = (short) (((s[1] & 0xF) << 2)); ++ t[3] = 64; ++ } ++ else { ++ t[2] = (short) (((s[1] & 0xF) << 2) + (s[2] >> 6)); ++ t[3] = (short) (s[2] & 0x3F); ++ } ++ for (int j = 0; j < 4; j++) ++ os.write(Base64.charAt(t[j])); ++ } ++ return new String(os.toByteArray()); ++} ++ ++/** ++ * Formats data into a nicely formatted base64 encoded String ++ * @param b An array containing binary data ++ * @param lineLength The number of characters per line ++ * @param prefix A string prefixing the characters on each line ++ * @param addClose Whether to add a close parenthesis or not ++ * @return A String representing the formatted output ++ */ ++public static String ++formatString(byte [] b, int lineLength, String prefix, boolean addClose) { ++ String s = toString(b); ++ StringBuffer sb = new StringBuffer(); ++ for (int i = 0; i < s.length(); i += lineLength) { ++ sb.append (prefix); ++ if (i + lineLength >= s.length()) { ++ sb.append(s.substring(i)); ++ if (addClose) ++ sb.append(" )"); ++ } ++ else { ++ sb.append(s.substring(i, i + lineLength)); ++ sb.append("\n"); ++ } ++ } ++ return sb.toString(); ++} ++ ++ ++/** ++ * Convert a base64-encoded String to binary data ++ * @param str A String containing the encoded data ++ * @return An array containing the binary data, or null if the string is invalid ++ */ ++public static byte [] ++fromString(String str) { ++ ByteArrayOutputStream bs = new ByteArrayOutputStream(); ++ byte [] raw = str.getBytes(); ++ for (int i = 0; i < raw.length; i++) { ++ if (!Character.isWhitespace((char)raw[i])) ++ bs.write(raw[i]); ++ } ++ byte [] in = bs.toByteArray(); ++ if (in.length % 4 != 0) { ++ return null; ++ } ++ ++ bs.reset(); ++ DataOutputStream ds = new DataOutputStream(bs); ++ ++ for (int i = 0; i < (in.length + 3) / 4; i++) { ++ short [] s = new short[4]; ++ short [] t = new short[3]; ++ ++ for (int j = 0; j < 4; j++) ++ s[j] = (short) Base64.indexOf(in[i*4+j]); ++ ++ t[0] = (short) ((s[0] << 2) + (s[1] >> 4)); ++ if (s[2] == 64) { ++ t[1] = t[2] = (short) (-1); ++ if ((s[1] & 0xF) != 0) ++ return null; ++ } ++ else if (s[3] == 64) { ++ t[1] = (short) (((s[1] << 4) + (s[2] >> 2)) & 0xFF); ++ t[2] = (short) (-1); ++ if ((s[2] & 0x3) != 0) ++ return null; ++ } ++ else { ++ t[1] = (short) (((s[1] << 4) + (s[2] >> 2)) & 0xFF); ++ t[2] = (short) (((s[2] << 6) + s[3]) & 0xFF); ++ } ++ ++ try { ++ for (int j = 0; j < 3; j++) ++ if (t[j] >= 0) ++ ds.writeByte(t[j]); ++ } ++ catch (IOException e) { ++ } ++ } ++ return bs.toByteArray(); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/utils/hexdump.java b/external/asmack/build/src/trunk/org/xbill/DNS/utils/hexdump.java +new file mode 100644 +index 0000000..1a79a40 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/utils/hexdump.java +@@ -0,0 +1,56 @@ ++// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) ++ ++package org.xbill.DNS.utils; ++ ++/** ++ * A routine to produce a nice looking hex dump ++ * ++ * @author Brian Wellington ++ */ ++ ++public class hexdump { ++ ++private static final char [] hex = "0123456789ABCDEF".toCharArray(); ++ ++/** ++ * Dumps a byte array into hex format. ++ * @param description If not null, a description of the data. ++ * @param b The data to be printed. ++ * @param offset The start of the data in the array. ++ * @param length The length of the data in the array. ++ */ ++public static String ++dump(String description, byte [] b, int offset, int length) { ++ StringBuffer sb = new StringBuffer(); ++ ++ sb.append(length + "b"); ++ if (description != null) ++ sb.append(" (" + description + ")"); ++ sb.append(':'); ++ ++ int prefixlen = sb.toString().length(); ++ prefixlen = (prefixlen + 8) & ~ 7; ++ sb.append('\t'); ++ ++ int perline = (80 - prefixlen) / 3; ++ for (int i = 0; i < length; i++) { ++ if (i != 0 && i % perline == 0) { ++ sb.append('\n'); ++ for (int j = 0; j < prefixlen / 8 ; j++) ++ sb.append('\t'); ++ } ++ int value = (int)(b[i + offset]) & 0xFF; ++ sb.append(hex[(value >> 4)]); ++ sb.append(hex[(value & 0xF)]); ++ sb.append(' '); ++ } ++ sb.append('\n'); ++ return sb.toString(); ++} ++ ++public static String ++dump(String s, byte [] b) { ++ return dump(s, b, 0, b.length); ++} ++ ++} +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer.properties b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer.properties +new file mode 100644 +index 0000000..25342f9 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer.properties +@@ -0,0 +1,4 @@ ++host_name=Host Name ++primary_dns_suffix=Primary Dns Suffix ++dns_suffix=DNS Suffix ++dns_servers=DNS Servers +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_de.properties b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_de.properties +new file mode 100644 +index 0000000..aa3f4a6 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_de.properties +@@ -0,0 +1,4 @@ ++host_name=Hostname ++primary_dns_suffix=Prim\u00E4res DNS-Suffix ++dns_suffix=DNS-Suffixsuchliste ++dns_servers=DNS-Server +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_fr.properties b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_fr.properties +new file mode 100644 +index 0000000..7c87a25 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_fr.properties +@@ -0,0 +1,4 @@ ++host_name=Nom de l'h\u00F4te ++primary_dns_suffix=Suffixe DNS principal ++dns_suffix=Suffixe DNS propre \u00E0 la connexion ++dns_servers=Serveurs DNS +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_ja.properties b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_ja.properties +new file mode 100644 +index 0000000..f873164 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_ja.properties +@@ -0,0 +1,4 @@ ++host_name=\u30db\u30b9\u30c8\u540d ++primary_dns_suffix=\u30d7\u30e9\u30a4\u30de\u30ea DNS \u30b5\u30d5\u30a3\u30c3\u30af\u30b9 ++dns_suffix=DNS \u30b5\u30d5\u30a3\u30c3\u30af\u30b9 ++dns_servers=DNS \u30b5\u30fc\u30d0\u30fc +diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_pl.properties b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_pl.properties +new file mode 100644 +index 0000000..eab5774 +--- /dev/null ++++ b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_pl.properties +@@ -0,0 +1,4 @@ ++host_name=Nazwa hosta ++primary_dns_suffix=Sufiks podstawowej domeny DNS ++dns_suffix=Sufiks DNS konkretnego po\u0142\u0105czenia ++dns_servers=Serwery DNS +diff --git a/external/asmack/build/src/trunk/overview.html b/external/asmack/build/src/trunk/overview.html +new file mode 100644 +index 0000000..a2449b1 +--- /dev/null ++++ b/external/asmack/build/src/trunk/overview.html +@@ -0,0 +1,4 @@ ++API specification for Smack, an Open Source XMPP client library. ++

      ++The {@link org.jivesoftware.smack.Connection} class is the main entry point for the API. ++ +diff --git a/external/asmack/build/src/trunk/samples/DiscoverServiceTypes.java b/external/asmack/build/src/trunk/samples/DiscoverServiceTypes.java +new file mode 100644 +index 0000000..eff0d05 +--- /dev/null ++++ b/external/asmack/build/src/trunk/samples/DiscoverServiceTypes.java +@@ -0,0 +1,70 @@ ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++// %Z%%M%, %I%, %G% ++// ++// This library is free software; you can redistribute it and/or ++// modify it under the terms of the GNU Lesser General Public ++// License as published by the Free Software Foundation; either ++// version 2.1 of the License, or (at your option) any later version. ++// ++// This library is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++// Lesser General Public License for more details. ++// ++// You should have received a copy of the GNU Lesser General Public ++// License along with this library; if not, write to the Free Software ++// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ ++package samples; ++ ++import java.io.IOException; ++ ++import org.jmdns.JmDNS; ++import org.jmdns.ServiceEvent; ++import org.jmdns.ServiceTypeListener; ++ ++/** ++ * Sample Code for Service Type Discovery using JmDNS and a ServiceTypeListener. ++ *

      ++ * Run the main method of this class. It lists all service types known on the ++ * local network on System.out. ++ * ++ * @author Werner Randelshofer ++ * @version %I%, %G% ++ */ ++public class DiscoverServiceTypes { ++ ++ static class SampleListener implements ServiceTypeListener { ++ ++ public void serviceTypeAdded(ServiceEvent event) { ++ System.out.println("Service type added: " +event.getType()); ++ } ++ } ++ /** ++ * @param args the command line arguments ++ */ ++ public static void main(String[] args) { ++ /* Activate these lines to see log messages of JmDNS ++ Logger logger = Logger.getLogger(JmDNS.class.getName()); ++ ConsoleHandler handler = new ConsoleHandler(); ++ logger.addHandler(handler); ++ logger.setLevel(Level.FINER); ++ handler.setLevel(Level.FINER); ++ */ ++ ++ try { ++ JmDNS jmdns = JmDNS.create(); ++ jmdns.addServiceTypeListener(new SampleListener()); ++ ++ System.out.println("Press q and Enter, to quit"); ++ int b; ++ while ((b = System.in.read()) != -1 && (char) b != 'q'); ++ jmdns.close(); ++ System.out.println("Done"); ++ } catch (IOException e) { ++ e.printStackTrace(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/samples/DiscoverServices.java b/external/asmack/build/src/trunk/samples/DiscoverServices.java +new file mode 100644 +index 0000000..612f3ac +--- /dev/null ++++ b/external/asmack/build/src/trunk/samples/DiscoverServices.java +@@ -0,0 +1,74 @@ ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++// %Z%%M%, %I%, %G% ++// ++// This library is free software; you can redistribute it and/or ++// modify it under the terms of the GNU Lesser General Public ++// License as published by the Free Software Foundation; either ++// version 2.1 of the License, or (at your option) any later version. ++// ++// This library is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++// Lesser General Public License for more details. ++// ++// You should have received a copy of the GNU Lesser General Public ++// License along with this library; if not, write to the Free Software ++// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ ++package samples; ++ ++import java.io.IOException; ++ ++import org.jmdns.JmDNS; ++import org.jmdns.ServiceEvent; ++import org.jmdns.ServiceListener; ++ ++/** ++ * Sample Code for Service Discovery using JmDNS and a ServiceListener. ++ *

      ++ * Run the main method of this class. It listens for HTTP services and lists ++ * all changes on System.out. ++ * ++ * @author Werner Randelshofer ++ * @version %I%, %G% ++ */ ++public class DiscoverServices { ++ ++ static class SampleListener implements ServiceListener { ++ public void serviceAdded(ServiceEvent event) { ++ System.out.println("Service added : " + event.getName()+"."+event.getType()); ++ } ++ public void serviceRemoved(ServiceEvent event) { ++ System.out.println("Service removed : " + event.getName()+"."+event.getType()); ++ } ++ public void serviceResolved(ServiceEvent event) { ++ System.out.println("Service resolved: " + event.getInfo()); ++ } ++ } ++ /** ++ * @param args the command line arguments ++ */ ++ public static void main(String[] args) { ++ try { ++ /* Activate these lines to see log messages of JmDNS ++ Logger logger = Logger.getLogger(JmDNS.class.getName()); ++ ConsoleHandler handler = new ConsoleHandler(); ++ logger.addHandler(handler); ++ logger.setLevel(Level.FINER); ++ handler.setLevel(Level.FINER); ++ */ ++ JmDNS jmdns = JmDNS.create(); ++ jmdns.addServiceListener("_http._tcp.local.", new SampleListener()); ++ ++ System.out.println("Press q and Enter, to quit"); ++ int b; ++ while ((b = System.in.read()) != -1 && (char) b != 'q'); ++ jmdns.close(); ++ System.out.println("Done"); ++ } catch (IOException e) { ++ e.printStackTrace(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/samples/ListServices.java b/external/asmack/build/src/trunk/samples/ListServices.java +new file mode 100644 +index 0000000..5bbfba8 +--- /dev/null ++++ b/external/asmack/build/src/trunk/samples/ListServices.java +@@ -0,0 +1,71 @@ ++// %Z%%M%, %I%, %G% ++// ++ ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++ ++// This library is free software; you can redistribute it and/or ++// modify it under the terms of the GNU Lesser General Public ++// License as published by the Free Software Foundation; either ++// version 2.1 of the License, or (at your option) any later version. ++// ++// This library is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++// Lesser General Public License for more details. ++// ++// You should have received a copy of the GNU Lesser General Public ++// License along with this library; if not, write to the Free Software ++// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ ++package samples; ++ ++import java.io.IOException; ++ ++import org.jmdns.JmDNS; ++import org.jmdns.ServiceInfo; ++ ++/** ++ * Sample Code for Listing Services using JmDNS. ++ *

      ++ * Run the main method of this class. This class prints a list of available HTTP ++ * services every 5 seconds. ++ * ++ * @author Werner Randelshofer ++ * @version %I%, %G% ++ */ ++public class ListServices { ++ ++ /** ++ * @param args the command line arguments ++ */ ++ public static void main(String[] args) { ++ /* Activate these lines to see log messages of JmDNS ++ Logger logger = Logger.getLogger(JmDNS.class.getName()); ++ ConsoleHandler handler = new ConsoleHandler(); ++ logger.addHandler(handler); ++ logger.setLevel(Level.FINER); ++ handler.setLevel(Level.FINER); ++ */ ++ ++ try { ++ JmDNS jmdns = JmDNS.create(); ++ while (true) { ++ ServiceInfo[] infos = jmdns.list("_http._tcp.local."); ++ for (int i=0; i < infos.length; i++) { ++ System.out.println(infos[i]); ++ } ++ System.out.println(); ++ ++ try { ++ Thread.sleep(5000); ++ } catch (InterruptedException e) { ++ break; ++ } ++ } ++ } catch (IOException e) { ++ e.printStackTrace(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/samples/OpenJmDNS.java b/external/asmack/build/src/trunk/samples/OpenJmDNS.java +new file mode 100644 +index 0000000..46cdcf0 +--- /dev/null ++++ b/external/asmack/build/src/trunk/samples/OpenJmDNS.java +@@ -0,0 +1,51 @@ ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++// %Z%%M%, %I%, %G% ++// ++// This library is free software; you can redistribute it and/or ++// modify it under the terms of the GNU Lesser General Public ++// License as published by the Free Software Foundation; either ++// version 2.1 of the License, or (at your option) any later version. ++// ++// This library is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++// Lesser General Public License for more details. ++// ++// You should have received a copy of the GNU Lesser General Public ++// License along with this library; if not, write to the Free Software ++// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ ++package samples; ++ ++import java.io.IOException; ++ ++import org.jmdns.JmDNS; ++ ++/** ++ * Sample Code that opens JmDNS in debug mode. ++ *

      ++ * Run the main method of this class. ++ * ++ * @author Werner Randelshofer ++ * @version %I%, %G% ++ */ ++public class OpenJmDNS { ++ /** ++ * @param args the command line arguments ++ */ ++ public static void main(String[] args) { ++ try { ++ System.setProperty("jmdns.debug", "2"); ++ JmDNS jmdns = JmDNS.create(); ++ ++ System.out.println("Press q and Enter, to quit"); ++ int b; ++ while ((b = System.in.read()) != -1 && (char) b != 'q'); ++ jmdns.close(); ++ } catch (IOException e) { ++ e.printStackTrace(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/samples/RegisterService.java b/external/asmack/build/src/trunk/samples/RegisterService.java +new file mode 100644 +index 0000000..324374e +--- /dev/null ++++ b/external/asmack/build/src/trunk/samples/RegisterService.java +@@ -0,0 +1,87 @@ ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++// %Z%%M%, %I%, %G% ++// ++// This library is free software; you can redistribute it and/or ++// modify it under the terms of the GNU Lesser General Public ++// License as published by the Free Software Foundation; either ++// version 2.1 of the License, or (at your option) any later version. ++// ++// This library is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++// Lesser General Public License for more details. ++// ++// You should have received a copy of the GNU Lesser General Public ++// License along with this library; if not, write to the Free Software ++// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ ++package samples; ++ ++import java.io.IOException; ++ ++import org.jmdns.JmDNS; ++import org.jmdns.ServiceInfo; ++ ++/** ++ * Sample Code for Service Registration using JmDNS. ++ *

      ++ * To see what happens, launch the TTY browser of JmDNS using the following ++ * command: ++ *

      ++ * java -jar lib/jmdns.jar -bs _http._tcp local.
      ++ * 
      ++ * Then run the main method of this class. When you press 'r' and enter, ++ * you should see the following output on the TTY browser: ++ *
      ++ * ADD: service[foo._http._tcp.local.,192.168.2.5:1234,path=index.html]
      ++ * 
      ++ * ++ * Press 'r' and enter, ++ * you should see the following output on the TTY browser: ++ *
      ++ * ADD: service[foo._http._tcp.local.,192.168.2.5:1234,path=index.html]
      ++ * 
      ++ * REMOVE: foo ++ * ++ * @author Werner Randelshofer ++ * @version %I%, %G% ++ */ ++public class RegisterService { ++ ++ /** ++ * @param args the command line arguments ++ */ ++ public static void main(String[] args) { ++ /* Activate these lines to see log messages of JmDNS ++ Logger logger = Logger.getLogger(JmDNS.class.getName()); ++ ConsoleHandler handler = new ConsoleHandler(); ++ logger.addHandler(handler); ++ logger.setLevel(Level.FINER); ++ handler.setLevel(Level.FINER); ++ */ ++ ++ try { ++ System.out.println("Opening JmDNS"); ++ JmDNS jmdns = JmDNS.create(); ++ System.out.println("Opened JmDNS"); ++ System.out.println("\nPress r and Enter, to register HTML service 'foo'"); ++ int b; ++ while ((b = System.in.read()) != -1 && (char) b != 'r'); ++ ServiceInfo info = ServiceInfo.create("_http._tcp.local.", "foo", 1268, 0, 0, "path=index.html"); ++ jmdns.registerService(info); ++ ++ System.out.println("\nRegistered Service as "+info); ++ System.out.println("Press q and Enter, to quit"); ++ //int b; ++ while ((b = System.in.read()) != -1 && (char) b != 'q'); ++ System.out.println("Closing JmDNS"); ++ jmdns.close(); ++ System.out.println("Done"); ++ System.exit(0); ++ } catch (IOException e) { ++ e.printStackTrace(); ++ } ++ } ++} +diff --git a/external/asmack/build/src/trunk/samples/TestShutdownHook.java b/external/asmack/build/src/trunk/samples/TestShutdownHook.java +new file mode 100644 +index 0000000..3728aa7 +--- /dev/null ++++ b/external/asmack/build/src/trunk/samples/TestShutdownHook.java +@@ -0,0 +1,33 @@ ++//Licensed under Apache License version 2.0 ++//Original license LGPL ++ ++package samples; ++ ++import java.io.*; ++/** ++ * TestShutdownHook. ++ * ++ * @author Werner Randelshofer ++ * @version 1.0 May 24, 2004 Created. ++ */ ++public class TestShutdownHook { ++ ++ /** Creates a new instance. */ ++ public static void main(String[] args) { ++ Runtime.getRuntime().addShutdownHook( ++ new Thread() { ++ public void run() { ++ System.out.println("Shutdown Hook"); ++ } ++ } ++ ); ++ try { ++ int b; ++ while ((b=System.in.read()) != -1) { ++ System.out.print("\""+(char) b); ++ } ++ } catch (IOException e) { ++ } ++ } ++ ++} +diff --git a/external/asmack/build/src/trunk/samples/package.html b/external/asmack/build/src/trunk/samples/package.html +new file mode 100644 +index 0000000..190f6aa +--- /dev/null ++++ b/external/asmack/build/src/trunk/samples/package.html +@@ -0,0 +1,59 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++Contains sample programs which demonstrate how to use the API of JmDNS. ++ ++ ++ ++

      Related Documentation

      ++ ++ ++For overviews, tutorials, examples, guides, and tool documentation, please see ++the Readme file of JmDNS. ++ ++ ++ ++ ++ ++