Add first attempt at JID class
This commit is contained in:
parent
b88d2ede67
commit
8e23b6c272
151
src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java
Normal file
151
src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
package eu.siacs.conversations.xmpp.jid;
|
||||||
|
|
||||||
|
import java.net.IDN;
|
||||||
|
|
||||||
|
import gnu.inet.encoding.Stringprep;
|
||||||
|
import gnu.inet.encoding.StringprepException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Jid' class provides an immutable representation of a JID.
|
||||||
|
*/
|
||||||
|
public final class Jid {
|
||||||
|
|
||||||
|
public final static class InvalidJidException extends Exception { }
|
||||||
|
|
||||||
|
private final String localpart;
|
||||||
|
private final String domainpart;
|
||||||
|
private final String resourcepart;
|
||||||
|
|
||||||
|
// It's much more efficient to store the ful JID as well as the parts instead of figuring them
|
||||||
|
// all out every time (since some characters are displayed but aren't used for comparisons).
|
||||||
|
private final String displayjid;
|
||||||
|
|
||||||
|
public String getLocalpart() {
|
||||||
|
return IDN.toUnicode(localpart);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDomainpart() {
|
||||||
|
return IDN.toUnicode(domainpart);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResourcepart() {
|
||||||
|
return IDN.toUnicode(resourcepart);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special private constructor that doesn't do any checking...
|
||||||
|
private Jid(final String localpart, final String domainpart) {
|
||||||
|
this.localpart = localpart;
|
||||||
|
this.domainpart = domainpart;
|
||||||
|
this.resourcepart = "";
|
||||||
|
if (localpart.isEmpty()) {
|
||||||
|
this.displayjid = domainpart;
|
||||||
|
} else {
|
||||||
|
this.displayjid = localpart + "@" + domainpart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: If introducing a mutable instance variable for some reason, make the constructor
|
||||||
|
// private and add a factory method to ensure thread safety and hash-cach-ability (tm).
|
||||||
|
public Jid(final String jid) throws InvalidJidException {
|
||||||
|
|
||||||
|
// Hackish Android way to count the number of chars in a string... should work everywhere.
|
||||||
|
final int atCount = jid.length() - jid.replace("@", "").length();
|
||||||
|
final int slashCount = jid.length() - jid.replace("/", "").length();
|
||||||
|
|
||||||
|
// Throw an error if there's anything obvious wrong with the JID...
|
||||||
|
if (atCount > 1 || slashCount > 1 ||
|
||||||
|
jid.length() == 0 || jid.length() > 3071 ||
|
||||||
|
jid.startsWith("@") || jid.endsWith("@") ||
|
||||||
|
jid.startsWith("/") || jid.endsWith("/")) {
|
||||||
|
throw new InvalidJidException();
|
||||||
|
}
|
||||||
|
|
||||||
|
String finaljid;
|
||||||
|
|
||||||
|
final int domainpartStart;
|
||||||
|
if (atCount == 1) {
|
||||||
|
final int atLoc = jid.indexOf("@");
|
||||||
|
final String lp = jid.substring(0, atLoc);
|
||||||
|
try {
|
||||||
|
localpart = Stringprep.nodeprep(lp);
|
||||||
|
} catch (final StringprepException e) {
|
||||||
|
throw new InvalidJidException();
|
||||||
|
}
|
||||||
|
if (localpart.isEmpty() || localpart.length() > 1023) {
|
||||||
|
throw new InvalidJidException();
|
||||||
|
}
|
||||||
|
domainpartStart = atLoc;
|
||||||
|
finaljid = lp + "@";
|
||||||
|
} else {
|
||||||
|
localpart = "";
|
||||||
|
finaljid = "";
|
||||||
|
domainpartStart = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String dp;
|
||||||
|
if (slashCount == 1) {
|
||||||
|
final int slashLoc = jid.indexOf("/");
|
||||||
|
final String rp = jid.substring(slashLoc + 1, jid.length());
|
||||||
|
try {
|
||||||
|
resourcepart = Stringprep.resourceprep(rp);
|
||||||
|
} catch (final StringprepException e) {
|
||||||
|
throw new InvalidJidException();
|
||||||
|
}
|
||||||
|
if (resourcepart.isEmpty() || resourcepart.length() > 1023) {
|
||||||
|
throw new InvalidJidException();
|
||||||
|
}
|
||||||
|
dp = jid.substring(domainpartStart, slashLoc);
|
||||||
|
finaljid = finaljid + dp + "/" + rp;
|
||||||
|
} else {
|
||||||
|
resourcepart = "";
|
||||||
|
dp = jid.substring(domainpartStart, jid.length());
|
||||||
|
finaljid = finaljid + dp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove trailling "." before storing the domain part.
|
||||||
|
if (dp.endsWith(".")) {
|
||||||
|
domainpart = IDN.toASCII(dp.substring(0, dp.length() - 1), IDN.USE_STD3_ASCII_RULES);
|
||||||
|
} else {
|
||||||
|
domainpart = IDN.toASCII(dp, IDN.USE_STD3_ASCII_RULES);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Find a proper domain validation library; validate individual parts, separators, etc.
|
||||||
|
if (domainpart.isEmpty() || domainpart.length() > 1023) {
|
||||||
|
throw new InvalidJidException();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.displayjid = finaljid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Jid getBareJid() {
|
||||||
|
return displayjid.contains("/") ? new Jid(localpart, domainpart) : this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return displayjid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
final Jid jid = (Jid) o;
|
||||||
|
|
||||||
|
// Since we're immutable, the JVM will cache hashcodes, making this very fast.
|
||||||
|
// I'm assuming Dalvik does the same sorts of optimizations...
|
||||||
|
// Since the hashcode does not include the displayJID it can be used for IDN comparison as
|
||||||
|
// well.
|
||||||
|
return jid.hashCode() == this.hashCode();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = localpart.hashCode();
|
||||||
|
result = 31 * result + domainpart.hashCode();
|
||||||
|
result = 31 * result + resourcepart.hashCode();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue