Commit 81c91ade authored by guus's avatar guus

JID class cache not longer uses one single mutex to synchronize all access on (JM-1453).

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@10838 b35dd754-fafc-0310-a699-88a17e54d16e
parent 758b9306
/** /**
* Copyright (C) 2004-2008 Jive Software. All rights reserved. * Copyright (C) 2004-2008 Jive Software. All rights reserved.
* *
* This software is published under the terms of the GNU Public License (GPL), * This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution, or a commercial license * a copy of which is included in this distribution, or a commercial license
* agreement with Jive. * agreement with Jive.
*/ */
package org.xmpp.packet; package org.xmpp.packet;
import org.jivesoftware.stringprep.IDNA; import org.jivesoftware.stringprep.IDNA;
import org.jivesoftware.stringprep.Stringprep; import org.jivesoftware.stringprep.Stringprep;
import org.jivesoftware.stringprep.StringprepException; import org.jivesoftware.stringprep.StringprepException;
import org.jivesoftware.util.cache.ExternalizableUtil; import org.jivesoftware.util.cache.ExternalizableUtil;
import java.io.Externalizable; import java.io.Externalizable;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInput; import java.io.ObjectInput;
import java.io.ObjectOutput; import java.io.ObjectOutput;
import java.util.Collections; import java.util.Map;
import java.util.LinkedHashMap; import java.util.Queue;
import java.util.Map; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* An XMPP address (JID). A JID is made up of a node (generally a username), a domain, /**
* and a resource. The node and resource are optional; domain is required. In simple * An XMPP address (JID). A JID is made up of a node (generally a username), a domain,
* ABNF form: * and a resource. The node and resource are optional; domain is required. In simple
* * ABNF form:
* <ul><tt>jid = [ node "@" ] domain [ "/" resource ]</tt></ul> *
* * <ul><tt>jid = [ node "@" ] domain [ "/" resource ]</tt></ul>
* Some sample JID's: *
* <ul> * Some sample JID's:
* <li><tt>user@example.com</tt></li> * <ul>
* <li><tt>user@example.com/home</tt></li> * <li><tt>user@example.com</tt></li>
* <li><tt>example.com</tt></li> * <li><tt>user@example.com/home</tt></li>
* </ul> * <li><tt>example.com</tt></li>
* * </ul>
* Each allowable portion of a JID (node, domain, and resource) must not be more *
* than 1023 bytes in length, resulting in a maximum total size (including the '@' * Each allowable portion of a JID (node, domain, and resource) must not be more
* and '/' separators) of 3071 bytes. * than 1023 bytes in length, resulting in a maximum total size (including the '@'
* * and '/' separators) of 3071 bytes.
* @author Matt Tucker *
*/ * @author Matt Tucker
public class JID implements Comparable<JID>, Externalizable { */
public class JID implements Comparable<JID>, Externalizable {
// Stringprep operations are very expensive. Therefore, we cache node, domain and
// resource values that have already had stringprep applied so that we can check // Stringprep operations are very expensive. Therefore, we cache node, domain and
// incoming values against the cache. // resource values that have already had stringprep applied so that we can check
private static Map<String,Object> stringprepCache = Collections.synchronizedMap(new Cache(10000)); // incoming values against the cache.
private static Cache<String> stringprepCache = new Cache<String>(10000);
private String node;
private String domain; private String node;
private String resource; private String domain;
private String resource;
private String cachedFullJID;
private String cachedBareJID; private String cachedFullJID;
private String cachedBareJID;
/**
* Escapes the node portion of a JID according to "JID Escaping" (JEP-0106). /**
* Escaping replaces characters prohibited by node-prep with escape sequences, * Escapes the node portion of a JID according to "JID Escaping" (JEP-0106).
* as follows:<p> * Escaping replaces characters prohibited by node-prep with escape sequences,
* * as follows:<p>
* <table border="1"> *
* <tr><td><b>Unescaped Character</b></td><td><b>Encoded Sequence</b></td></tr> * <table border="1">
* <tr><td>&lt;space&gt;</td><td>\20</td></tr> * <tr><td><b>Unescaped Character</b></td><td><b>Encoded Sequence</b></td></tr>
* <tr><td>"</td><td>\22</td></tr> * <tr><td>&lt;space&gt;</td><td>\20</td></tr>
* <tr><td>&</td><td>\26</td></tr> * <tr><td>"</td><td>\22</td></tr>
* <tr><td>'</td><td>\27</td></tr> * <tr><td>&</td><td>\26</td></tr>
* <tr><td>/</td><td>\2f</td></tr> * <tr><td>'</td><td>\27</td></tr>
* <tr><td>:</td><td>\3a</td></tr> * <tr><td>/</td><td>\2f</td></tr>
* <tr><td>&lt;</td><td>\3c</td></tr> * <tr><td>:</td><td>\3a</td></tr>
* <tr><td>&gt;</td><td>\3e</td></tr> * <tr><td>&lt;</td><td>\3c</td></tr>
* <tr><td>@</td><td>\40</td></tr> * <tr><td>&gt;</td><td>\3e</td></tr>
* <tr><td>\</td><td>\5c</td></tr> * <tr><td>@</td><td>\40</td></tr>
* </table><p> * <tr><td>\</td><td>\5c</td></tr>
* * </table><p>
* 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 * This process is useful when the node comes from an external source that doesn't
* the &lt;space&gt; character isn't a valid part of a node, the username should * conform to nodeprep. For example, a username in LDAP may be "Joe Smith". Because
* be escaped to "Joe\20Smith" before being made into a JID (e.g. "joe\20smith@example.com" * the &lt;space&gt; character isn't a valid part of a node, the username should
* after case-folding, etc. has been applied).<p> * be escaped to "Joe\20Smith" before being made into a JID (e.g. "joe\20smith@example.com"
* * after case-folding, etc. has been applied).<p>
* All node escaping and un-escaping must be performed manually at the appropriate *
* time; the JID class will not escape or un-escape automatically. * 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. * @param node the node.
*/ * @return the escaped version of the node.
public static String escapeNode(String node) { */
if (node == null) { public static String escapeNode(String node) {
return null; if (node == null) {
} return null;
StringBuilder buf = new StringBuilder(node.length() + 8); }
for (int i=0, n=node.length(); i<n; i++) { StringBuilder buf = new StringBuilder(node.length() + 8);
char c = node.charAt(i); for (int i=0, n=node.length(); i<n; i++) {
switch (c) { char c = node.charAt(i);
case '"': buf.append("\\22"); break; switch (c) {
case '&': buf.append("\\26"); break; case '"': buf.append("\\22"); break;
case '\'': buf.append("\\27"); break; case '&': buf.append("\\26"); break;
case '/': buf.append("\\2f"); break; case '\'': buf.append("\\27"); break;
case ':': buf.append("\\3a"); break; case '/': buf.append("\\2f"); break;
case '<': buf.append("\\3c"); break; case ':': buf.append("\\3a"); break;
case '>': buf.append("\\3e"); break; case '<': buf.append("\\3c"); break;
case '@': buf.append("\\40"); break; case '>': buf.append("\\3e"); break;
case '\\': buf.append("\\5c"); break; case '@': buf.append("\\40"); break;
default: { case '\\': buf.append("\\5c"); break;
if (Character.isWhitespace(c)) { default: {
buf.append("\\20"); if (Character.isWhitespace(c)) {
} buf.append("\\20");
else { }
buf.append(c); else {
} buf.append(c);
} }
} }
} }
return buf.toString(); }
} return buf.toString();
}
/**
* Un-escapes the node portion of a JID according to "JID Escaping" (JEP-0106).<p> /**
* Escaping replaces characters prohibited by node-prep with escape sequences, * Un-escapes the node portion of a JID according to "JID Escaping" (JEP-0106).<p>
* as follows:<p> * Escaping replaces characters prohibited by node-prep with escape sequences,
* * as follows:<p>
* <table border="1"> *
* <tr><td><b>Unescaped Character</b></td><td><b>Encoded Sequence</b></td></tr> * <table border="1">
* <tr><td>&lt;space&gt;</td><td>\20</td></tr> * <tr><td><b>Unescaped Character</b></td><td><b>Encoded Sequence</b></td></tr>
* <tr><td>"</td><td>\22</td></tr> * <tr><td>&lt;space&gt;</td><td>\20</td></tr>
* <tr><td>&</td><td>\26</td></tr> * <tr><td>"</td><td>\22</td></tr>
* <tr><td>'</td><td>\27</td></tr> * <tr><td>&</td><td>\26</td></tr>
* <tr><td>/</td><td>\2f</td></tr> * <tr><td>'</td><td>\27</td></tr>
* <tr><td>:</td><td>\3a</td></tr> * <tr><td>/</td><td>\2f</td></tr>
* <tr><td>&lt;</td><td>\3c</td></tr> * <tr><td>:</td><td>\3a</td></tr>
* <tr><td>&gt;</td><td>\3e</td></tr> * <tr><td>&lt;</td><td>\3c</td></tr>
* <tr><td>@</td><td>\40</td></tr> * <tr><td>&gt;</td><td>\3e</td></tr>
* <tr><td>\</td><td>\5c</td></tr> * <tr><td>@</td><td>\40</td></tr>
* </table><p> * <tr><td>\</td><td>\5c</td></tr>
* * </table><p>
* 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 * This process is useful when the node comes from an external source that doesn't
* the &lt;space&gt; character isn't a valid part of a node, the username should * conform to nodeprep. For example, a username in LDAP may be "Joe Smith". Because
* be escaped to "Joe\20Smith" before being made into a JID (e.g. "joe\20smith@example.com" * the &lt;space&gt; character isn't a valid part of a node, the username should
* after case-folding, etc. has been applied).<p> * be escaped to "Joe\20Smith" before being made into a JID (e.g. "joe\20smith@example.com"
* * after case-folding, etc. has been applied).<p>
* All node escaping and un-escaping must be performed manually at the appropriate *
* time; the JID class will not escape or un-escape automatically. * 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. * @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) { public static String unescapeNode(String node) {
return null; if (node == null) {
} return null;
char [] nodeChars = node.toCharArray(); }
StringBuilder buf = new StringBuilder(nodeChars.length); char [] nodeChars = node.toCharArray();
for (int i=0, n=nodeChars.length; i<n; i++) { StringBuilder buf = new StringBuilder(nodeChars.length);
compare: { for (int i=0, n=nodeChars.length; i<n; i++) {
char c = node.charAt(i); compare: {
if (c == '\\' && i+2<n) { char c = node.charAt(i);
char c2 = nodeChars[i+1]; if (c == '\\' && i+2<n) {
char c3 = nodeChars[i+2]; char c2 = nodeChars[i+1];
if (c2 == '2') { char c3 = nodeChars[i+2];
switch (c3) { if (c2 == '2') {
case '0': buf.append(' '); i+=2; break compare; switch (c3) {
case '2': buf.append('"'); i+=2; break compare; case '0': buf.append(' '); i+=2; break compare;
case '6': buf.append('&'); i+=2; break compare; case '2': buf.append('"'); i+=2; break compare;
case '7': buf.append('\''); i+=2; break compare; case '6': buf.append('&'); i+=2; break compare;
case 'f': buf.append('/'); i+=2; break compare; case '7': buf.append('\''); i+=2; break compare;
} case 'f': buf.append('/'); i+=2; break compare;
} }
else if (c2 == '3') { }
switch (c3) { else if (c2 == '3') {
case 'a': buf.append(':'); i+=2; break compare; switch (c3) {
case 'c': buf.append('<'); i+=2; break compare; case 'a': buf.append(':'); i+=2; break compare;
case 'e': buf.append('>'); i+=2; break compare; case 'c': buf.append('<'); i+=2; break compare;
} case 'e': buf.append('>'); i+=2; break compare;
} }
else if (c2 == '4') { }
if (c3 == '0') { else if (c2 == '4') {
buf.append("@"); if (c3 == '0') {
i+=2; buf.append("@");
break compare; i+=2;
} break compare;
} }
else if (c2 == '5') { }
if (c3 == 'c') { else if (c2 == '5') {
buf.append("\\"); if (c3 == 'c') {
i+=2; buf.append("\\");
break compare; i+=2;
} break compare;
} }
} }
buf.append(c); }
} buf.append(c);
} }
return buf.toString(); }
} return buf.toString();
}
public static String resourceprep(String resource) throws StringprepException {
String answer = resource; public static String resourceprep(String resource) throws StringprepException {
if (!stringprepCache.containsKey(resource)) { String answer = resource;
answer = Stringprep.resourceprep(resource); if (!stringprepCache.contains(resource)) {
// Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes. answer = Stringprep.resourceprep(resource);
if (answer != null && answer.length()*2 > 1023) { // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
return answer; if (answer != null && answer.length()*2 > 1023) {
} return answer;
stringprepCache.put(answer, null); }
} stringprepCache.put(answer);
return answer; }
} return answer;
}
/**
* Constructor added for Externalizable. Do not use this constructor. /**
*/ * Constructor added for Externalizable. Do not use this constructor.
public JID() { */
} public JID() {
}
/**
* Constructs a JID from it's String representation. /**
* * Constructs a JID from it's String representation.
* @param jid a valid JID. *
* @throws IllegalArgumentException if the JID is not valid. * @param jid a valid JID.
*/ * @throws IllegalArgumentException if the JID is not valid.
public JID(String jid) { */
if (jid == null) { public JID(String jid) {
throw new NullPointerException("JID cannot be null"); if (jid == null) {
} throw new NullPointerException("JID cannot be null");
String[] parts = getParts(jid); }
String[] parts = getParts(jid);
init(parts[0], parts[1], parts[2]);
} init(parts[0], parts[1], parts[2]);
}
/**
* Constructs a JID given a node, domain, and resource. /**
* * Constructs a JID given a node, domain, and resource.
* @param node the node. *
* @param domain the domain, which must not be <tt>null</tt>. * @param node the node.
* @param resource the resource. * @param domain the domain, which must not be <tt>null</tt>.
* @throws IllegalArgumentException if the JID is not valid. * @param resource the resource.
*/ * @throws IllegalArgumentException if the JID is not valid.
public JID(String node, String domain, String resource) { */
if (domain == null) { public JID(String node, String domain, String resource) {
throw new NullPointerException("Domain cannot be null"); if (domain == null) {
} throw new NullPointerException("Domain cannot be null");
init(node, domain, resource); }
} init(node, domain, resource);
}
/**
* Constructs a JID given a node, domain, and resource being able to specify if stringprep /**
* should be applied or not. * Constructs a JID given a node, domain, and resource being able to specify if stringprep
* * should be applied or not.
* @param node the node. *
* @param domain the domain, which must not be <tt>null</tt>. * @param node the node.
* @param resource the resource. * @param domain the domain, which must not be <tt>null</tt>.
* @param skipStringprep true if stringprep should not be applied. * @param resource the resource.
* @throws IllegalArgumentException if the JID is not valid. * @param skipStringprep true if stringprep should not be applied.
*/ * @throws IllegalArgumentException if the JID is not valid.
public JID(String node, String domain, String resource, boolean skipStringprep) { */
if (domain == null) { public JID(String node, String domain, String resource, boolean skipStringprep) {
throw new NullPointerException("Domain cannot be null"); if (domain == null) {
} throw new NullPointerException("Domain cannot be null");
if (skipStringprep) { }
this.node = node; if (skipStringprep) {
this.domain = domain; this.node = node;
this.resource = resource; this.domain = domain;
// Cache the bare and full JID String representation this.resource = resource;
updateCache(); // Cache the bare and full JID String representation
} updateCache();
else { }
init(node, domain, resource); else {
} init(node, domain, resource);
} }
}
/**
* Returns a String array with the parsed node, domain and resource. /**
* No Stringprep is performed while parsing the textual representation. * Returns a String array with the parsed node, domain and resource.
* * No Stringprep is performed while parsing the textual representation.
* @param jid the textual JID representation. *
* @return a string array with the parsed node, domain and resource. * @param jid the textual JID representation.
*/ * @return a string array with the parsed node, domain and resource.
static String[] getParts(String jid) { */
String[] parts = new String[3]; static String[] getParts(String jid) {
String node = null , domain, resource; String[] parts = new String[3];
if (jid == null) { String node = null , domain, resource;
return parts; if (jid == null) {
} return parts;
}
int atIndex = jid.indexOf("@");
int slashIndex = jid.indexOf("/"); int atIndex = jid.indexOf("@");
int slashIndex = jid.indexOf("/");
// Node
if (atIndex > 0) { // Node
node = jid.substring(0, atIndex); if (atIndex > 0) {
} node = jid.substring(0, atIndex);
}
// Domain
if (atIndex + 1 > jid.length()) { // Domain
throw new IllegalArgumentException("JID with empty domain not valid"); if (atIndex + 1 > jid.length()) {
} throw new IllegalArgumentException("JID with empty domain not valid");
if (atIndex < 0) { }
if (slashIndex > 0) { if (atIndex < 0) {
domain = jid.substring(0, slashIndex); if (slashIndex > 0) {
} domain = jid.substring(0, slashIndex);
else { }
domain = jid; else {
} domain = jid;
} }
else { }
if (slashIndex > 0) { else {
domain = jid.substring(atIndex + 1, slashIndex); if (slashIndex > 0) {
} domain = jid.substring(atIndex + 1, slashIndex);
else { }
domain = jid.substring(atIndex + 1); else {
} domain = jid.substring(atIndex + 1);
} }
}
// Resource
if (slashIndex + 1 > jid.length() || slashIndex < 0) { // Resource
resource = null; if (slashIndex + 1 > jid.length() || slashIndex < 0) {
} resource = null;
else { }
resource = jid.substring(slashIndex + 1); else {
} resource = jid.substring(slashIndex + 1);
parts[0] = node; }
parts[1] = domain; parts[0] = node;
parts[2] = resource; parts[1] = domain;
return parts; parts[2] = resource;
} return parts;
}
/**
* Transforms the JID parts using the appropriate Stringprep profiles, then /**
* validates them. If they are fully valid, the field values are saved, otherwise * Transforms the JID parts using the appropriate Stringprep profiles, then
* an IllegalArgumentException is thrown. * validates them. If they are fully valid, the field values are saved, otherwise
* * an IllegalArgumentException is thrown.
* @param node the node. *
* @param domain the domain. * @param node the node.
* @param resource the resource. * @param domain the domain.
*/ * @param resource the resource.
private void init(String node, String domain, String resource) { */
// Set node and resource to null if they are the empty string. private void init(String node, String domain, String resource) {
if (node != null && node.equals("")) { // Set node and resource to null if they are the empty string.
node = null; if (node != null && node.equals("")) {
} node = null;
if (resource != null && resource.equals("")) { }
resource = null; if (resource != null && resource.equals("")) {
} resource = null;
// Stringprep (node prep, resourceprep, etc). }
try { // Stringprep (node prep, resourceprep, etc).
if (!stringprepCache.containsKey(node)) { try {
this.node = Stringprep.nodeprep(node); if (!stringprepCache.contains(node)) {
// Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes. this.node = Stringprep.nodeprep(node);
if (this.node != null && this.node.length()*2 > 1023) { // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
throw new IllegalArgumentException("Node cannot be larger than 1023 bytes. " + if (this.node != null && this.node.length()*2 > 1023) {
"Size is " + (this.node.length() * 2) + " bytes."); throw new IllegalArgumentException("Node cannot be larger than 1023 bytes. " +
} "Size is " + (this.node.length() * 2) + " bytes.");
stringprepCache.put(this.node, null); }
} stringprepCache.put(this.node);
else { }
this.node = node; else {
} this.node = node;
// XMPP specifies that domains should be run through IDNA and }
// that they should be run through nameprep before doing any // XMPP specifies that domains should be run through IDNA and
// comparisons. We always run the domain through nameprep to // that they should be run through nameprep before doing any
// make comparisons easier later. // comparisons. We always run the domain through nameprep to
if (!stringprepCache.containsKey(domain)) { // make comparisons easier later.
this.domain = Stringprep.nameprep(IDNA.toASCII(domain), false); if (!stringprepCache.contains(domain)) {
// Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes. this.domain = Stringprep.nameprep(IDNA.toASCII(domain), false);
if (this.domain.length()*2 > 1023) { // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
throw new IllegalArgumentException("Domain cannot be larger than 1023 bytes. " + if (this.domain.length()*2 > 1023) {
"Size is " + (this.domain.length() * 2) + " bytes."); throw new IllegalArgumentException("Domain cannot be larger than 1023 bytes. " +
} "Size is " + (this.domain.length() * 2) + " bytes.");
stringprepCache.put(this.domain, null); }
} stringprepCache.put(this.domain);
else { }
this.domain = domain; else {
} this.domain = domain;
this.resource = resourceprep(resource); }
// Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes. this.resource = resourceprep(resource);
if (resource != null && resource.length()*2 > 1023) { // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
throw new IllegalArgumentException("Resource cannot be larger than 1023 bytes. " + if (resource != null && resource.length()*2 > 1023) {
"Size is " + (resource.length() * 2) + " bytes."); throw new IllegalArgumentException("Resource cannot be larger than 1023 bytes. " +
} "Size is " + (resource.length() * 2) + " bytes.");
// Cache the bare and full JID String representation }
updateCache(); // Cache the bare and full JID String representation
} updateCache();
catch (Exception e) { }
StringBuilder buf = new StringBuilder(); catch (Exception e) {
if (node != null) { StringBuilder buf = new StringBuilder();
buf.append(node).append("@"); if (node != null) {
} buf.append(node).append("@");
buf.append(domain); }
if (resource != null) { buf.append(domain);
buf.append("/").append(resource); if (resource != null) {
} buf.append("/").append(resource);
throw new IllegalArgumentException("Illegal JID: " + buf.toString(), e); }
} throw new IllegalArgumentException("Illegal JID: " + buf.toString(), e);
} }
}
private void updateCache() {
// Cache the bare JID private void updateCache() {
StringBuilder buf = new StringBuilder(40); // Cache the bare JID
if (node != null) { StringBuilder buf = new StringBuilder(40);
buf.append(node).append("@"); if (node != null) {
} buf.append(node).append("@");
buf.append(domain); }
cachedBareJID = buf.toString(); buf.append(domain);
cachedBareJID = buf.toString();
// Cache the full JID
if (resource != null) { // Cache the full JID
buf.append("/").append(resource); if (resource != null) {
cachedFullJID = buf.toString(); buf.append("/").append(resource);
} cachedFullJID = buf.toString();
else { }
cachedFullJID = cachedBareJID; else {
} cachedFullJID = cachedBareJID;
} }
}
/**
* Returns the node, or <tt>null</tt> if this JID does not contain node information. /**
* * Returns the node, or <tt>null</tt> if this JID does not contain node information.
* @return the node. *
*/ * @return the node.
public String getNode() { */
return node; public String getNode() {
} return node;
}
/**
* Returns the domain. /**
* * Returns the domain.
* @return the domain. *
*/ * @return the domain.
public String getDomain() { */
return domain; public String getDomain() {
} return domain;
}
/**
* Returns the resource, or <tt>null</tt> if this JID does not contain resource information. /**
* * Returns the resource, or <tt>null</tt> if this JID does not contain resource information.
* @return the resource. *
*/ * @return the resource.
public String getResource() { */
return resource; public String getResource() {
} return resource;
}
/**
* Returns the String representation of the bare JID, which is the JID with /**
* resource information removed. * Returns the String representation of the bare JID, which is the JID with
* * resource information removed.
* @return the bare JID. *
*/ * @return the bare JID.
public String toBareJID() { */
return cachedBareJID; public String toBareJID() {
} return cachedBareJID;
}
/**
* Returns a String representation of the JID. /**
* * Returns a String representation of the JID.
* @return a String representation of the JID. *
*/ * @return a String representation of the JID.
public String toString() { */
return cachedFullJID; public String toString() {
} return cachedFullJID;
}
public int hashCode() {
return toString().hashCode(); public int hashCode() {
} return toString().hashCode();
}
public boolean equals(Object object) {
if (!(object instanceof JID)) { public boolean equals(Object object) {
return false; if (!(object instanceof JID)) {
} return false;
if (this == object) { }
return true; if (this == object) {
} return true;
JID jid = (JID)object; }
// Node. If node isn't null, compare. JID jid = (JID)object;
if (node != null) { // Node. If node isn't null, compare.
if (!node.equals(jid.node)) { if (node != null) {
return false; if (!node.equals(jid.node)) {
} return false;
} }
// Otherwise, jid.node must be null. }
else if (jid.node != null) { // Otherwise, jid.node must be null.
return false; else if (jid.node != null) {
} return false;
// Compare domain, which must be null. }
if (!domain.equals(jid.domain)) { // Compare domain, which must be null.
return false; if (!domain.equals(jid.domain)) {
} return false;
// Resource. If resource isn't null, compare. }
if (resource != null) { // Resource. If resource isn't null, compare.
if (!resource.equals(jid.resource)) { if (resource != null) {
return false; if (!resource.equals(jid.resource)) {
} return false;
} }
// Otherwise, jid.resource must be null. }
else if (jid.resource != null) { // Otherwise, jid.resource must be null.
return false; else if (jid.resource != null) {
} return false;
// Passed all checks, so equal. }
return true; // Passed all checks, so equal.
} return true;
}
public int compareTo(JID jid) {
// Comparison order is domain, node, resource. public int compareTo(JID jid) {
int compare = domain.compareTo(jid.domain); // Comparison order is domain, node, resource.
if (compare == 0) { int compare = domain.compareTo(jid.domain);
String myNode = node != null ? node : ""; if (compare == 0) {
String hisNode = jid.node != null ? jid.node : ""; String myNode = node != null ? node : "";
compare = myNode.compareTo(hisNode); String hisNode = jid.node != null ? jid.node : "";
} compare = myNode.compareTo(hisNode);
if (compare == 0) { }
String myResource = resource != null ? resource : ""; if (compare == 0) {
String hisResource = jid.resource != null ? jid.resource : ""; String myResource = resource != null ? resource : "";
compare = myResource.compareTo(hisResource); String hisResource = jid.resource != null ? jid.resource : "";
} compare = myResource.compareTo(hisResource);
return compare; }
} return compare;
}
/**
* Returns true if two JID's are equivalent. The JID components are compared using /**
* the following rules:<ul> * Returns true if two JID's are equivalent. The JID components are compared using
* <li>Nodes are normalized using nodeprep (case insensitive). * the following rules:<ul>
* <li>Domains are normalized using IDNA and then nameprep (case insensitive). * <li>Nodes are normalized using nodeprep (case insensitive).
* <li>Resources are normalized using resourceprep (case sensitive).</ul> * <li>Domains are normalized using IDNA and then nameprep (case insensitive).
* * <li>Resources are normalized using resourceprep (case sensitive).</ul>
* These normalization rules ensure, for example, that *
* <tt>User@EXAMPLE.com/home</tt> is considered equal to <tt>user@example.com/home</tt>. * These normalization rules ensure, for example, that
* * <tt>User@EXAMPLE.com/home</tt> is considered equal to <tt>user@example.com/home</tt>.
* @param jid1 a JID. *
* @param jid2 a JID. * @param jid1 a JID.
* @return true if the JIDs are equivalent; false otherwise. * @param jid2 a JID.
* @throws IllegalArgumentException if either JID is not valid. * @return true if the JIDs are equivalent; false otherwise.
*/ * @throws IllegalArgumentException if either JID is not valid.
public static boolean equals(String jid1, String jid2) { */
return new JID(jid1).equals(new JID(jid2)); public static boolean equals(String jid1, String jid2) {
} return new JID(jid1).equals(new JID(jid2));
}
/**
* A simple cache class that extends LinkedHashMap. It uses an LRU policy to /**
* keep the cache at a maximum size. * A simple cache class with limited functionality. It uses an FIFO
*/ * eviction policy to keep the cache at a maximum size. This class
private static class Cache extends LinkedHashMap<String,Object> { * offers acceptable thread safety for the purpose of the parent class.
*
private int maxSize; * @author Guus der Kinderen, guus@nimbuzz.com
*/
public Cache(int maxSize) { private static class Cache<K> {
super(64, .75f, true);
this.maxSize = maxSize; /** Cannot add null values in ConcurrentHashMap... */
} private final static Object NULL = new Object();
protected boolean removeEldestEntry(Map.Entry eldest) { /** Queue that records insertion order. Used to implement FIFO behavior. */
return size() > maxSize; private final Queue<K> fifoQueue = new ConcurrentLinkedQueue<K>();
}
} /** Values are cached in a hashmap for fast lookup. **/
private final Map<K, Object> cachedValues = new ConcurrentHashMap<K, Object>();
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeSafeUTF(out, toString()); /** Cache capacity */
} private int maxSize;
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { /**
String jid = ExternalizableUtil.getInstance().readSafeUTF(in); * Constructs a new capacity-bound cache.
String[] parts = getParts(jid); *
* @param maxSize
this.node = parts[0]; * The maximum number of elements that the cache can contain.
this.domain = parts[1]; */
this.resource = parts[2]; public Cache(int maxSize) {
// Cache the bare and full JID String representation this.maxSize = maxSize;
updateCache(); }
}
/**
* Adds a new element to the cache. The cache is pruned if the maximum
* capacity has been reached.
*
* @param entry
* The element to be added to the cache
*/
public void put(K entry) {
synchronized (entry) {
// add value to the cache
if (cachedValues.put(entry, NULL) != null) {
// ensure that queue doesn't contain duplicates.
fifoQueue.offer(entry);
}
}
// apply eviction policy if required.
while (cachedValues.size() > maxSize) {
cachedValues.remove(fifoQueue.poll());
}
}
/**
* Checks if the cache contains an element.
*
* @param entry
* The element to check for.
* @return <tt>true</tt> if the cache currently contains the entry,
* <tt>false</tt> otherwise.
*/
public boolean contains(K entry) {
// no need to nodeprep null - it'll result in null.
if (entry == null) {
return true;
}
// Note that this method will need to record access if
// we want to switch to LRU.
return cachedValues.containsKey(entry);
}
}
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeSafeUTF(out, toString());
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
String jid = ExternalizableUtil.getInstance().readSafeUTF(in);
String[] parts = getParts(jid);
this.node = parts[0];
this.domain = parts[1];
this.resource = parts[2];
// Cache the bare and full JID String representation
updateCache();
}
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment