Jid.java

  1package eu.siacs.conversations.xmpp.jid;
  2
  3import java.net.IDN;
  4
  5import gnu.inet.encoding.Stringprep;
  6import gnu.inet.encoding.StringprepException;
  7
  8/**
  9 * The `Jid' class provides an immutable representation of a JID.
 10 */
 11public final class Jid {
 12
 13    private final String localpart;
 14    private final String domainpart;
 15    private final String resourcepart;
 16
 17    // It's much more efficient to store the ful JID as well as the parts instead of figuring them
 18    // all out every time (since some characters are displayed but aren't used for comparisons).
 19    private final String displayjid;
 20
 21    public String getLocalpart() {
 22        return IDN.toUnicode(localpart);
 23    }
 24
 25    public String getDomainpart() {
 26        return IDN.toUnicode(domainpart);
 27    }
 28
 29    public String getResourcepart() {
 30        return IDN.toUnicode(resourcepart);
 31    }
 32
 33    // Special private constructor that doesn't do any checking...
 34    private Jid(final String localpart, final String domainpart) {
 35        this.localpart = localpart;
 36        this.domainpart = domainpart;
 37        this.resourcepart = "";
 38        if (localpart.isEmpty()) {
 39            this.displayjid = domainpart;
 40        } else {
 41            this.displayjid = localpart + "@" + domainpart;
 42        }
 43    }
 44
 45    // Note: If introducing a mutable instance variable for some reason, make the constructor
 46    // private and add a factory method to ensure thread safety and hash-cach-ability (tm).
 47    public Jid(final String jid) throws InvalidJidException {
 48
 49        // Hackish Android way to count the number of chars in a string... should work everywhere.
 50        final int atCount = jid.length() - jid.replace("@", "").length();
 51        final int slashCount = jid.length() - jid.replace("/", "").length();
 52
 53        // Throw an error if there's anything obvious wrong with the JID...
 54        if (jid.isEmpty() || jid.length() > 3071) {
 55            throw new InvalidJidException(InvalidJidException.INVALID_LENGTH);
 56        }
 57        if (atCount > 1 || slashCount > 1 ||
 58                jid.startsWith("@") || jid.endsWith("@") ||
 59                jid.startsWith("/") || jid.endsWith("/")) {
 60            throw new InvalidJidException(InvalidJidException.INVALID_CHARACTER);
 61        }
 62
 63        String finaljid;
 64
 65        final int domainpartStart;
 66        if (atCount == 1) {
 67            final int atLoc = jid.indexOf("@");
 68            final String lp = jid.substring(0, atLoc);
 69            try {
 70                localpart = Stringprep.nodeprep(lp);
 71            } catch (final StringprepException e) {
 72                throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
 73            }
 74            if (localpart.isEmpty() || localpart.length() > 1023) {
 75                throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
 76            }
 77            domainpartStart = atLoc;
 78            finaljid = lp + "@";
 79        } else {
 80            localpart = "";
 81            finaljid = "";
 82            domainpartStart = 0;
 83        }
 84
 85        final String dp;
 86        if (slashCount == 1) {
 87            final int slashLoc = jid.indexOf("/");
 88            final String rp = jid.substring(slashLoc + 1, jid.length());
 89            try {
 90                resourcepart = Stringprep.resourceprep(rp);
 91            } catch (final StringprepException e) {
 92                throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
 93            }
 94            if (resourcepart.isEmpty() || resourcepart.length() > 1023) {
 95                throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
 96            }
 97            dp = jid.substring(domainpartStart, slashLoc);
 98            finaljid = finaljid + dp + "/" + rp;
 99        } else {
100            resourcepart = "";
101            dp = jid.substring(domainpartStart, jid.length());
102            finaljid = finaljid + dp;
103        }
104
105        // Remove trailling "." before storing the domain part.
106        if (dp.endsWith(".")) {
107            domainpart = IDN.toASCII(dp.substring(0, dp.length() - 1), IDN.USE_STD3_ASCII_RULES);
108        } else {
109            domainpart = IDN.toASCII(dp, IDN.USE_STD3_ASCII_RULES);
110        }
111
112        // TODO: Find a proper domain validation library; validate individual parts, separators, etc.
113        if (domainpart.isEmpty() || domainpart.length() > 1023) {
114            throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
115        }
116
117        this.displayjid = finaljid;
118    }
119
120    public Jid getBareJid() {
121        return displayjid.contains("/") ? new Jid(localpart, domainpart) : this;
122    }
123
124    @Override
125    public String toString() {
126        return displayjid;
127    }
128
129    @Override
130    public boolean equals(final Object o) {
131        if (this == o) return true;
132        if (o == null || getClass() != o.getClass()) return false;
133
134        final Jid jid = (Jid) o;
135
136        // Since we're immutable, the JVM will cache hashcodes, making this very fast.
137        // I'm assuming Dalvik does the same sorts of optimizations...
138        // Since the hashcode does not include the displayJID it can be used for IDN comparison as
139        // well.
140        return jid.hashCode() == this.hashCode();
141
142    }
143
144    @Override
145    public int hashCode() {
146        int result = localpart.hashCode();
147        result = 31 * result + domainpart.hashCode();
148        result = 31 * result + resourcepart.hashCode();
149        return result;
150    }
151}