KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > xmpp > packet > JID


1 /**
2  * $RCSfile: JID.java,v $
3  * $Revision: 1.16 $
4  * $Date: 2005/05/09 00:07:49 $
5  *
6  * Copyright 2004 Jive Software.
7  *
8  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */

20
21 package org.xmpp.packet;
22
23 import org.jivesoftware.stringprep.IDNA;
24 import org.jivesoftware.stringprep.Stringprep;
25
26 import java.util.*;
27
28 /**
29  * An XMPP address (JID). A JID is made up of a node (generally a username), a domain,
30  * and a resource. The node and resource are optional; domain is required. In simple
31  * ABNF form:
32  *
33  * <ul><tt>jid = [ node "@" ] domain [ "/" resource ]</tt></ul>
34  *
35  * Some sample JID's:
36  * <ul>
37  * <li><tt>user@example.com</tt></li>
38  * <li><tt>user@example.com/home</tt></li>
39  * <li><tt>example.com</tt></li>
40  * </ul>
41  *
42  * Each allowable portion of a JID (node, domain, and resource) must not be more
43  * than 1023 bytes in length, resulting in a maximum total size (including the '@'
44  * and '/' separators) of 3071 bytes.
45  *
46  * @author Matt Tucker
47  */

48 public class JID implements Comparable JavaDoc {
49
50     // Stringprep operations are very expensive. Therefore, we cache node, domain and
51
// resource values that have already had stringprep applied so that we can check
52
// incoming values against the cache.
53
private static Map stringprepCache = Collections.synchronizedMap(new Cache(1000));
54
55     private String JavaDoc node;
56     private String JavaDoc domain;
57     private String JavaDoc resource;
58
59     /**
60      * Escapes the node portion of a JID according to "JID Escaping" (JEP-0106).
61      * Escaping replaces characters prohibited by node-prep with escape sequences,
62      * as follows:<p>
63      *
64      * <table border="1">
65      * <tr><td><b>Unescaped Character</b></td><td><b>Encoded Sequence</b></td></tr>
66      * <tr><td>&lt;space&gt;</td><td>\20</td></tr>
67      * <tr><td>"</td><td>\22</td></tr>
68      * <tr><td>&</td><td>\26</td></tr>
69      * <tr><td>'</td><td>\27</td></tr>
70      * <tr><td>/</td><td>\2f</td></tr>
71      * <tr><td>:</td><td>\3a</td></tr>
72      * <tr><td>&lt;</td><td>\3c</td></tr>
73      * <tr><td>&gt;</td><td>\3e</td></tr>
74      * <tr><td>@</td><td>\40</td></tr>
75      * <tr><td>\</td><td>\5c</td></tr>
76      * </table><p>
77      *
78      * This process is useful when the node comes from an external source that doesn't
79      * conform to nodeprep. For example, a username in LDAP may be "Joe Smith". Because
80      * the &lt;space&gt; character isn't a valid part of a node, the username should
81      * be escaped to "Joe\20Smith" before being made into a JID (e.g. "joe\20smith@example.com"
82      * after case-folding, etc. has been applied).<p>
83      *
84      * All node escaping and un-escaping must be performed manually at the appropriate
85      * time; the JID class will not escape or un-escape automatically.
86      *
87      * @param node the node.
88      * @return the escaped version of the node.
89      */

90     public static String JavaDoc escapeNode(String JavaDoc node) {
91         if (node == null) {
92             return null;
93         }
94         StringBuilder JavaDoc buf = new StringBuilder JavaDoc(node.length() + 8);
95         for (int i=0, n=node.length(); i<n; i++) {
96             char c = node.charAt(i);
97             switch (c) {
98                 case '"': buf.append("\\22"); break;
99                 case '&': buf.append("\\26"); break;
100                 case '\'': buf.append("\\27"); break;
101                 case '/': buf.append("\\2f"); break;
102                 case ':': buf.append("\\3a"); break;
103                 case '<': buf.append("\\3c"); break;
104                 case '>': buf.append("\\3e"); break;
105                 case '@': buf.append("\\40"); break;
106                 case '\\': buf.append("\\5c"); break;
107                 default: {
108                     if (Character.isWhitespace(c)) {
109                         buf.append("\\20");
110                     }
111                     else {
112                         buf.append(c);
113                     }
114                 }
115             }
116         }
117         return buf.toString();
118     }
119
120     /**
121      * Un-escapes the node portion of a JID according to "JID Escaping" (JEP-0106).<p>
122      * Escaping replaces characters prohibited by node-prep with escape sequences,
123      * as follows:<p>
124      *
125      * <table border="1">
126      * <tr><td><b>Unescaped Character</b></td><td><b>Encoded Sequence</b></td></tr>
127      * <tr><td>&lt;space&gt;</td><td>\20</td></tr>
128      * <tr><td>"</td><td>\22</td></tr>
129      * <tr><td>&</td><td>\26</td></tr>
130      * <tr><td>'</td><td>\27</td></tr>
131      * <tr><td>/</td><td>\2f</td></tr>
132      * <tr><td>:</td><td>\3a</td></tr>
133      * <tr><td>&lt;</td><td>\3c</td></tr>
134      * <tr><td>&gt;</td><td>\3e</td></tr>
135      * <tr><td>@</td><td>\40</td></tr>
136      * <tr><td>\</td><td>\5c</td></tr>
137      * </table><p>
138      *
139      * This process is useful when the node comes from an external source that doesn't
140      * conform to nodeprep. For example, a username in LDAP may be "Joe Smith". Because
141      * the &lt;space&gt; character isn't a valid part of a node, the username should
142      * be escaped to "Joe\20Smith" before being made into a JID (e.g. "joe\20smith@example.com"
143      * after case-folding, etc. has been applied).<p>
144      *
145      * All node escaping and un-escaping must be performed manually at the appropriate
146      * time; the JID class will not escape or un-escape automatically.
147      *
148      * @param node the escaped version of the node.
149      * @return the un-escaped version of the node.
150      */

151     public static String JavaDoc unescapeNode(String JavaDoc node) {
152         if (node == null) {
153             return null;
154         }
155         char [] nodeChars = node.toCharArray();
156         StringBuilder JavaDoc buf = new StringBuilder JavaDoc(nodeChars.length);
157         for (int i=0, n=nodeChars.length; i<n; i++) {
158             compare: {
159                 char c = node.charAt(i);
160                 if (c == '\\' && i+2<n) {
161                     char c2 = nodeChars[i+1];
162                     char c3 = nodeChars[i+2];
163                     if (c2 == '2') {
164                         switch (c3) {
165                             case '0': buf.append(' '); i+=2; break compare;
166                             case '2': buf.append('"'); i+=2; break compare;
167                             case '6': buf.append('&'); i+=2; break compare;
168                             case '7': buf.append('\''); i+=2; break compare;
169                             case 'f': buf.append('/'); i+=2; break compare;
170                         }
171                     }
172                     else if (c2 == '3') {
173                         switch (c3) {
174                             case 'a': buf.append(':'); i+=2; break compare;
175                             case 'c': buf.append('<'); i+=2; break compare;
176                             case 'e': buf.append('>'); i+=2; break compare;
177                         }
178                     }
179                     else if (c2 == '4') {
180                         if (c3 == '0') {
181                             buf.append("@");
182                             i+=2;
183                             break compare;
184                         }
185                     }
186                     else if (c2 == '5') {
187                         if (c3 == 'c') {
188                             buf.append("\\");
189                             i+=2;
190                             break compare;
191                         }
192                     }
193                 }
194                 buf.append(c);
195             }
196         }
197         return buf.toString();
198     }
199
200     /**
201      * Constructs a JID from it's String representation.
202      *
203      * @param jid a valid JID.
204      * @throws IllegalArgumentException if the JID is not valid.
205      */

206     public JID(String JavaDoc jid) {
207         if (jid == null) {
208             throw new NullPointerException JavaDoc("JID cannot be null");
209         }
210         String JavaDoc node = null;
211         String JavaDoc domain = null;
212         String JavaDoc resource = null;
213
214         int atIndex = jid.indexOf("@");
215         int slashIndex = jid.indexOf("/");
216
217         // Node
218
if (atIndex > 0) {
219             node = jid.substring(0, atIndex);
220         }
221
222         // Domain
223
if (atIndex + 1 > jid.length()) {
224             throw new IllegalArgumentException JavaDoc("JID with empty domain not valid");
225         }
226         if (atIndex < 0) {
227             if (slashIndex > 0) {
228                 domain = jid.substring(0, slashIndex);
229             }
230             else {
231                 domain = jid;
232             }
233         }
234         else {
235             if (slashIndex > 0) {
236                 domain = jid.substring(atIndex + 1, slashIndex);
237             }
238             else {
239                 domain = jid.substring(atIndex + 1);
240             }
241         }
242
243         // Resource
244
if (slashIndex + 1 > jid.length() || slashIndex < 0) {
245             resource = null;
246         }
247         else {
248             resource = jid.substring(slashIndex + 1);
249         }
250
251         init(node, domain,resource);
252     }
253
254     /**
255      * Constructs a JID given a node, domain, and resource.
256      *
257      * @param node the node.
258      * @param domain the domain, which must not be <tt>null</tt>.
259      * @param resource the resource.
260      * @throws IllegalArgumentException if the JID is not valid.
261      */

262     public JID(String JavaDoc node, String JavaDoc domain, String JavaDoc resource) {
263         if (domain == null) {
264             throw new NullPointerException JavaDoc("Domain cannot be null");
265         }
266         init(node, domain, resource);
267     }
268
269     /**
270      * Constructs a new JID, bypassing all stringprep profiles. This
271      * is useful for constructing a JID object when it's already known
272      * that the String representation is well-formed.
273      *
274      * @param jid the JID.
275      * @param fake an extra param to create a different method signature.
276      * The value <tt>null</tt> should be passed in as this argument.
277      */

278     JID(String JavaDoc jid, Object JavaDoc fake) {
279         fake = null; // Workaround IDE warnings for unused param.
280
if (jid == null) {
281             throw new NullPointerException JavaDoc("JID cannot be null");
282         }
283
284         int atIndex = jid.indexOf("@");
285         int slashIndex = jid.indexOf("/");
286
287         // Node
288
if (atIndex > 0) {
289             node = jid.substring(0, atIndex);
290         }
291
292         // Domain
293
if (atIndex + 1 > jid.length()) {
294             throw new IllegalArgumentException JavaDoc("JID with empty domain not valid");
295         }
296         if (atIndex < 0) {
297             if (slashIndex > 0) {
298                 domain = jid.substring(0, slashIndex);
299             }
300             else {
301                 domain = jid;
302             }
303         }
304         else {
305             if (slashIndex > 0) {
306                 domain = jid.substring(atIndex + 1, slashIndex);
307             }
308             else {
309                 domain = jid.substring(atIndex + 1);
310             }
311         }
312
313         // Resource
314
if (slashIndex + 1 > jid.length() || slashIndex < 0) {
315             resource = null;
316         }
317         else {
318             resource = jid.substring(slashIndex + 1);
319         }
320     }
321
322     /**
323      * Transforms the JID parts using the appropriate Stringprep profiles, then
324      * validates them. If they are fully valid, the field values are saved, otherwise
325      * an IllegalArgumentException is thrown.
326      *
327      * @param node the node.
328      * @param domain the domain.
329      * @param resource the resource.
330      */

331     private void init(String JavaDoc node, String JavaDoc domain, String JavaDoc resource) {
332         // Set node and resource to null if they are the empty string.
333
if (node != null && node.equals("")) {
334             node = null;
335         }
336         if (resource != null && resource.equals("")) {
337             resource = null;
338         }
339         // Stringprep (node prep, resourceprep, etc).
340
try {
341             if (!stringprepCache.containsKey(node)) {
342                 this.node = Stringprep.nodeprep(node);
343                 // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
344
if (node != null && node.length()*2 > 1023) {
345                     throw new IllegalArgumentException JavaDoc("Node cannot be larger than 1023 bytes. " +
346                             "Size is " + (node.length() * 2) + " bytes.");
347                 }
348                 stringprepCache.put(this.node, null);
349             }
350             else {
351                 this.node = node;
352             }
353             // XMPP specifies that domains should be run through IDNA and
354
// that they should be run through nameprep before doing any
355
// comparisons. We always run the domain through nameprep to
356
// make comparisons easier later.
357
if (!stringprepCache.containsKey(domain)) {
358                 this.domain = Stringprep.nameprep(IDNA.toASCII(domain), false);
359                 // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
360
if (domain.length()*2 > 1023) {
361                     throw new IllegalArgumentException JavaDoc("Domain cannot be larger than 1023 bytes. " +
362                             "Size is " + (domain.length() * 2) + " bytes.");
363                 }
364                 stringprepCache.put(this.domain, null);
365             }
366             else {
367                 this.domain = domain;
368             }
369             if (!stringprepCache.containsKey(resource)) {
370                 this.resource = Stringprep.resourceprep(resource);
371                 // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
372
if (resource != null && resource.length()*2 > 1023) {
373                     throw new IllegalArgumentException JavaDoc("Resource cannot be larger than 1023 bytes. " +
374                             "Size is " + (resource.length() * 2) + " bytes.");
375                 }
376                 stringprepCache.put(this.resource, null);
377             }
378             else {
379                 this.resource = resource;
380             }
381         }
382         catch (Exception JavaDoc e) {
383             StringBuilder JavaDoc buf = new StringBuilder JavaDoc();
384             if (node != null) {
385                 buf.append(node).append("@");
386             }
387             buf.append(domain);
388             if (resource != null) {
389                 buf.append("/").append(resource);
390             }
391             throw new IllegalArgumentException JavaDoc("Illegal JID: " + buf.toString(), e);
392         }
393     }
394
395     /**
396      * Returns the node, or <tt>null</tt> if this JID does not contain node information.
397      *
398      * @return the node.
399      */

400     public String JavaDoc getNode() {
401         return node;
402     }
403
404     /**
405      * Returns the domain.
406      *
407      * @return the domain.
408      */

409     public String JavaDoc getDomain() {
410         return domain;
411     }
412
413     /**
414      * Returns the resource, or <tt>null</tt> if this JID does not contain resource information.
415      *
416      * @return the resource.
417      */

418     public String JavaDoc getResource() {
419         return resource;
420     }
421
422     /**
423      * Returns the String representation of the bare JID, which is the JID with
424      * resource information removed.
425      *
426      * @return the bare JID.
427      */

428     public String JavaDoc toBareJID() {
429         StringBuilder JavaDoc buf = new StringBuilder JavaDoc();
430         if (node != null) {
431             buf.append(node).append("@");
432         }
433         buf.append(domain);
434         return buf.toString();
435     }
436
437     /**
438      * Returns a String representation of the JID.
439      *
440      * @return a String representation of the JID.
441      */

442     public String JavaDoc toString() {
443         StringBuilder JavaDoc buf = new StringBuilder JavaDoc();
444         if (node != null) {
445             buf.append(node).append("@");
446         }
447         buf.append(domain);
448         if (resource != null) {
449             buf.append("/").append(resource);
450         }
451         return buf.toString();
452     }
453
454     public int hashCode() {
455         return toString().hashCode();
456     }
457
458     public boolean equals(Object JavaDoc object) {
459         if (!(object instanceof JID)) {
460             return false;
461         }
462         if (this == object) {
463             return true;
464         }
465         JID jid = (JID)object;
466         // Node. If node isn't null, compare.
467
if (node != null) {
468             if (!node.equals(jid.node)) {
469                 return false;
470             }
471         }
472         // Otherwise, jid.node must be null.
473
else if (jid.node != null) {
474             return false;
475         }
476         // Compare domain, which must be null.
477
if (!domain.equals(jid.domain)) {
478             return false;
479         }
480         // Resource. If resource isn't null, compare.
481
if (resource != null) {
482             if (!resource.equals(jid.resource)) {
483                 return false;
484             }
485         }
486         // Otherwise, jid.resource must be null.
487
else if (jid.resource != null) {
488             return false;
489         }
490         // Passed all checks, so equal.
491
return true;
492     }
493
494     public int compareTo(Object JavaDoc o) {
495         if (!(o instanceof JID)) {
496             throw new ClassCastException JavaDoc("Ojbect not instanceof JID: " + o);
497         }
498         JID jid = (JID)o;
499
500         // Comparison order is domain, node, resource.
501
int compare = domain.compareTo(jid.domain);
502         if (compare == 0 && node != null && jid.node != null) {
503             compare = node.compareTo(jid.node);
504         }
505         if (compare == 0 && resource != null && jid.resource != null) {
506             compare = resource.compareTo(jid.resource);
507         }
508         return compare;
509     }
510
511     /**
512      * Returns true if two JID's are equivalent. The JID components are compared using
513      * the following rules:<ul>
514      * <li>Nodes are normalized using nodeprep (case insensitive).
515      * <li>Domains are normalized using IDNA and then nameprep (case insensitive).
516      * <li>Resources are normalized using resourceprep (case sensitive).</ul>
517      *
518      * These normalization rules ensure, for example, that
519      * <tt>User@EXAMPLE.com/home</tt> is considered equal to <tt>user@example.com/home</tt>.
520      *
521      * @param jid1 a JID.
522      * @param jid2 a JID.
523      * @return true if the JIDs are equivalent; false otherwise.
524      * @throws IllegalArgumentException if either JID is not valid.
525      */

526     public static boolean equals(String JavaDoc jid1, String JavaDoc jid2) {
527         return new JID(jid1).equals(new JID(jid2));
528     }
529
530     /**
531      * A simple cache class that extends LinkedHashMap. It uses an LRU policy to
532      * keep the cache at a maximum size.
533      */

534     private static class Cache extends LinkedHashMap {
535
536         private int maxSize;
537
538         public Cache(int maxSize) {
539             super(64, .75f, true);
540             this.maxSize = maxSize;
541         }
542
543         protected boolean removeEldestEntry(Map.Entry eldest) {
544             return size() > maxSize;
545         }
546     }
547 }
Popular Tags