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    public final static class InvalidJidException extends Exception { }
 14
 15    private final String localpart;
 16    private final String domainpart;
 17    private final String resourcepart;
 18
 19    // It's much more efficient to store the ful JID as well as the parts instead of figuring them
 20    // all out every time (since some characters are displayed but aren't used for comparisons).
 21    private final String displayjid;
 22
 23    public String getLocalpart() {
 24        return IDN.toUnicode(localpart);
 25    }
 26
 27    public String getDomainpart() {
 28        return IDN.toUnicode(domainpart);
 29    }
 30
 31    public String getResourcepart() {
 32        return IDN.toUnicode(resourcepart);
 33    }
 34
 35    // Special private constructor that doesn't do any checking...
 36    private Jid(final String localpart, final String domainpart) {
 37        this.localpart = localpart;
 38        this.domainpart = domainpart;
 39        this.resourcepart = "";
 40        if (localpart.isEmpty()) {
 41            this.displayjid = domainpart;
 42        } else {
 43            this.displayjid = localpart + "@" + domainpart;
 44        }
 45    }
 46
 47    // Note: If introducing a mutable instance variable for some reason, make the constructor
 48    // private and add a factory method to ensure thread safety and hash-cach-ability (tm).
 49    public Jid(final String jid) throws InvalidJidException {
 50
 51        // Hackish Android way to count the number of chars in a string... should work everywhere.
 52        final int atCount = jid.length() - jid.replace("@", "").length();
 53        final int slashCount = jid.length() - jid.replace("/", "").length();
 54
 55        // Throw an error if there's anything obvious wrong with the JID...
 56        if (atCount > 1 || slashCount > 1 ||
 57                jid.length() == 0 || jid.length() > 3071 ||
 58                jid.startsWith("@") || jid.endsWith("@") ||
 59                jid.startsWith("/") || jid.endsWith("/")) {
 60            throw new InvalidJidException();
 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();
 73            }
 74            if (localpart.isEmpty() || localpart.length() > 1023) {
 75                throw new InvalidJidException();
 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();
 93            }
 94            if (resourcepart.isEmpty() || resourcepart.length() > 1023) {
 95                throw new InvalidJidException();
 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();
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}