61 * meaning would be ambiguous. 62 *<p> 63 * <code>LdapName</code> will properly parse all valid names, but 64 * does not attempt to detect all possible violations when parsing 65 * invalid names. It's "generous". 66 *<p> 67 * When names are tested for equality, attribute types and binary 68 * values are case-insensitive, and string values are by default 69 * case-insensitive. 70 * String values with different but equivalent usage of quoting, 71 * escaping, or UTF8-hex-encoding are considered equal. The order of 72 * components in multi-valued RDNs (such as "ou=Sales+cn=Bob") is not 73 * significant. 74 * 75 * @author Scott Seligman 76 */ 77 78 public final class LdapName implements Name { 79 80 private transient String unparsed; // if non-null, the DN in unparsed form 81 private transient Vector rdns; // parsed name components 82 private transient boolean valuesCaseSensitive = false; 83 84 /** 85 * Constructs an LDAP name from the given DN. 86 * 87 * @param name An LDAP DN. To JNDI, a compound name. 88 * 89 * @throws InvalidNameException if a syntax violation is detected. 90 */ 91 public LdapName(String name) throws InvalidNameException { 92 unparsed = name; 93 parse(); 94 } 95 96 /* 97 * Constructs an LDAP name given its parsed components and, optionally 98 * (if "name" is not null), the unparsed DN. 99 */ 100 private LdapName(String name, Vector rdns) { 101 unparsed = name; 102 this.rdns = (Vector)rdns.clone(); 103 } 104 105 /* 106 * Constructs an LDAP name given its parsed components (the elements 107 * of "rdns" in the range [beg,end)) and, optionally 108 * (if "name" is not null), the unparsed DN. 109 */ 110 private LdapName(String name, Vector rdns, int beg, int end) { 111 unparsed = name; 112 this.rdns = new Vector(); 113 for (int i = beg; i < end; i++) { 114 this.rdns.addElement(rdns.elementAt(i)); 115 } 116 } 117 118 119 public Object clone() { 120 return new LdapName(unparsed, rdns); 121 } 122 123 public String toString() { 124 if (unparsed != null) { 125 return unparsed; 126 } 127 128 StringBuffer buf = new StringBuffer(); 129 for (int i = rdns.size() - 1; i >= 0; i--) { 130 if (i < rdns.size() - 1) { 131 buf.append(','); 132 } 133 Rdn rdn = (Rdn)rdns.elementAt(i); 134 buf.append(rdn); 135 } 136 137 unparsed = new String(buf); 138 return unparsed; 139 } 140 141 public boolean equals(Object obj) { 142 return ((obj instanceof LdapName) && 143 (compareTo(obj) == 0)); 144 } 145 146 public int compareTo(Object obj) { 147 LdapName that = (LdapName)obj; 148 149 if ((obj == this) || // check possible shortcuts 150 (unparsed != null && unparsed.equals(that.unparsed))) { 151 return 0; 152 } 153 154 // Compare RDNs one by one, lexicographically. 155 int minSize = Math.min(rdns.size(), that.rdns.size()); 156 for (int i = 0 ; i < minSize; i++) { 157 // Compare a single pair of RDNs. 158 Rdn rdn1 = (Rdn)rdns.elementAt(i); 159 Rdn rdn2 = (Rdn)that.rdns.elementAt(i); 160 161 int diff = rdn1.compareTo(rdn2); 162 if (diff != 0) { 163 return diff; 164 } 165 } 166 return (rdns.size() - that.rdns.size()); // longer DN wins 167 } 168 169 public int hashCode() { 170 // Sum up the hash codes of the components. 171 int hash = 0; 172 173 // For each RDN... 174 for (int i = 0; i < rdns.size(); i++) { 175 Rdn rdn = (Rdn)rdns.elementAt(i); 176 hash += rdn.hashCode(); 177 } 178 return hash; 179 } 180 181 public int size() { 182 return rdns.size(); 183 } 184 185 public boolean isEmpty() { 186 return rdns.isEmpty(); 187 } 188 189 public Enumeration getAll() { 190 final Enumeration enum_ = rdns.elements(); 191 192 return new Enumeration () { 193 public boolean hasMoreElements() { 194 return enum_.hasMoreElements(); 195 } 196 public Object nextElement() { 197 return enum_.nextElement().toString(); 198 } 199 }; 200 } 201 202 public String get(int pos) { 203 return rdns.elementAt(pos).toString(); 204 } 205 206 public Name getPrefix(int pos) { 207 return new LdapName(null, rdns, 0, pos); 208 } 209 210 public Name getSuffix(int pos) { 211 return new LdapName(null, rdns, pos, rdns.size()); 212 } 213 214 public boolean startsWith(Name n) { 215 int len1 = rdns.size(); 216 int len2 = n.size(); 237 parse(); 238 } catch (InvalidNameException e) { 239 // shouldn't happen 240 throw new IllegalStateException("Cannot parse name: " + unparsed); 241 } 242 valuesCaseSensitive = caseSensitive; 243 } 244 245 /* 246 * Helper method for startsWith() and endsWith(). 247 * Returns true if components [beg,end) match the components of "n". 248 * If "n" is not an LdapName, each of its components is parsed as 249 * the string form of an RDN. 250 * The following must hold: end - beg == n.size(). 251 */ 252 private boolean matches(int beg, int end, Name n) { 253 for (int i = beg; i < end; i++) { 254 Rdn rdn; 255 if (n instanceof LdapName) { 256 LdapName ln = (LdapName)n; 257 rdn = (Rdn)ln.rdns.elementAt(i - beg); 258 } else { 259 String rdnString = n.get(i - beg); 260 try { 261 rdn = (new DnParser(rdnString, valuesCaseSensitive)).getRdn(); 262 } catch (InvalidNameException e) { 263 return false; 264 } 265 } 266 267 if (!rdn.equals(rdns.elementAt(i))) { 268 return false; 269 } 270 } 271 return true; 272 } 273 274 public Name addAll(Name suffix) throws InvalidNameException { 275 return addAll(size(), suffix); 276 } 277 278 /* 279 * If "suffix" is not an LdapName, each of its components is parsed as 280 * the string form of an RDN. 281 */ 282 public Name addAll(int pos, Name suffix) throws InvalidNameException { 283 if (suffix instanceof LdapName) { 284 LdapName s = (LdapName)suffix; 285 for (int i = 0; i < s.rdns.size(); i++) { 286 rdns.insertElementAt(s.rdns.elementAt(i), pos++); 287 } 288 } else { 289 Enumeration comps = suffix.getAll(); 290 while (comps.hasMoreElements()) { 291 DnParser p = new DnParser((String)comps.nextElement(), 292 valuesCaseSensitive); 293 rdns.insertElementAt(p.getRdn(), pos++); 294 } 295 } 296 unparsed = null; // no longer valid 297 return this; 298 } 299 300 public Name add(String comp) throws InvalidNameException { 301 return add(size(), comp); 302 } 303 304 public Name add(int pos, String comp) throws InvalidNameException { 305 Rdn rdn = (new DnParser(comp, valuesCaseSensitive)).getRdn(); 306 rdns.insertElementAt(rdn, pos); 307 unparsed = null; // no longer valid 308 return this; 309 } 310 311 public Object remove(int pos) throws InvalidNameException { 389 private final String name; // DN being parsed 390 private final char[] chars; // characters in LDAP name being parsed 391 private final int len; // length of "chars" 392 private int cur = 0; // index of first unconsumed char in "chars" 393 private boolean valuesCaseSensitive; 394 395 /* 396 * Given an LDAP DN in string form, returns a parser for it. 397 */ 398 DnParser(String name, boolean valuesCaseSensitive) 399 throws InvalidNameException { 400 this.name = name; 401 len = name.length(); 402 chars = name.toCharArray(); 403 this.valuesCaseSensitive = valuesCaseSensitive; 404 } 405 406 /* 407 * Parses the DN, returning a Vector of its RDNs. 408 */ 409 Vector getDn() throws InvalidNameException { 410 cur = 0; 411 Vector rdns = new Vector(len / 3 + 10); // leave room for growth 412 413 if (len == 0) { 414 return rdns; 415 } 416 417 rdns.addElement(parseRdn()); 418 while (cur < len) { 419 if (chars[cur] == ',' || chars[cur] == ';') { 420 ++cur; 421 rdns.insertElementAt(parseRdn(), 0); 422 } else { 423 throw new InvalidNameException("Invalid name: " + name); 424 } 425 } 426 return rdns; 427 } 428 429 /* 430 * Parses the DN, if it is known to contain a single RDN. 431 */ 578 * a string attribute value. 579 */ 580 private boolean atTerminator() { 581 return (cur < len && 582 (chars[cur] == ',' || 583 chars[cur] == ';' || 584 chars[cur] == '+')); 585 } 586 } 587 588 589 /* 590 * Class Rdn represents a set of TypeAndValue. 591 */ 592 static class Rdn { 593 594 /* 595 * A vector of the TypeAndValue elements of this Rdn. 596 * It is sorted to facilitate set operations. 597 */ 598 private final Vector tvs = new Vector(); 599 600 void add(TypeAndValue tv) { 601 602 // Set i to index of first element greater than tv, or to 603 // tvs.size() if there is none. 604 int i; 605 for (i = 0; i < tvs.size(); i++) { 606 int diff = tv.compareTo(tvs.elementAt(i)); 607 if (diff == 0) { 608 return; // tv is a duplicate: ignore it 609 } else if (diff < 0) { 610 break; 611 } 612 } 613 614 tvs.insertElementAt(tv, i); 615 } 616 617 public String toString() { 618 StringBuffer buf = new StringBuffer(); 619 for (int i = 0; i < tvs.size(); i++) { 620 if (i > 0) { 621 buf.append('+'); 622 } 623 buf.append(tvs.elementAt(i)); 624 } 625 return new String(buf); 626 } 627 628 public boolean equals(Object obj) { 629 return ((obj instanceof Rdn) && 630 (compareTo(obj) == 0)); 631 } 632 633 // Compare TypeAndValue components one by one, lexicographically. 634 public int compareTo(Object obj) { 635 Rdn that = (Rdn)obj; 636 int minSize = Math.min(tvs.size(), that.tvs.size()); 637 for (int i = 0; i < minSize; i++) { 638 // Compare a single pair of type/value pairs. 639 TypeAndValue tv = (TypeAndValue)tvs.elementAt(i); 640 int diff = tv.compareTo(that.tvs.elementAt(i)); 641 if (diff != 0) { 642 return diff; 643 } 644 } 645 return (tvs.size() - that.tvs.size()); // longer RDN wins 646 } 647 648 public int hashCode() { 649 // Sum up the hash codes of the components. 650 int hash = 0; 651 652 // For each type/value pair... 653 for (int i = 0; i < tvs.size(); i++) { 654 hash += tvs.elementAt(i).hashCode(); 655 } 656 return hash; 657 } 658 659 Attributes toAttributes() { 660 Attributes attrs = new BasicAttributes(true); 661 TypeAndValue tv; 662 Attribute attr; 663 664 for (int i = 0; i < tvs.size(); i++) { 665 tv = (TypeAndValue) tvs.elementAt(i); 666 if ((attr = attrs.get(tv.getType())) == null) { 667 attrs.put(tv.getType(), tv.getUnescapedValue()); 668 } else { 669 attr.add(tv.getUnescapedValue()); 670 } 671 } 672 return attrs; 673 } 674 } 675 676 677 /* 678 * Class TypeAndValue represents an attribute type and its 679 * corresponding value. 680 */ 681 static class TypeAndValue { 682 683 private final String type; 684 private final String value; // value, escaped or quoted 685 private final boolean binary; | 61 * meaning would be ambiguous. 62 *<p> 63 * <code>LdapName</code> will properly parse all valid names, but 64 * does not attempt to detect all possible violations when parsing 65 * invalid names. It's "generous". 66 *<p> 67 * When names are tested for equality, attribute types and binary 68 * values are case-insensitive, and string values are by default 69 * case-insensitive. 70 * String values with different but equivalent usage of quoting, 71 * escaping, or UTF8-hex-encoding are considered equal. The order of 72 * components in multi-valued RDNs (such as "ou=Sales+cn=Bob") is not 73 * significant. 74 * 75 * @author Scott Seligman 76 */ 77 78 public final class LdapName implements Name { 79 80 private transient String unparsed; // if non-null, the DN in unparsed form 81 private transient Vector<Rdn> rdns; // parsed name components 82 private transient boolean valuesCaseSensitive = false; 83 84 /** 85 * Constructs an LDAP name from the given DN. 86 * 87 * @param name An LDAP DN. To JNDI, a compound name. 88 * 89 * @throws InvalidNameException if a syntax violation is detected. 90 */ 91 public LdapName(String name) throws InvalidNameException { 92 unparsed = name; 93 parse(); 94 } 95 96 /* 97 * Constructs an LDAP name given its parsed components and, optionally 98 * (if "name" is not null), the unparsed DN. 99 */ 100 @SuppressWarnings("unchecked") 101 private LdapName(String name, Vector<Rdn> rdns) { 102 unparsed = name; 103 this.rdns = (Vector<Rdn>)rdns.clone(); 104 } 105 106 /* 107 * Constructs an LDAP name given its parsed components (the elements 108 * of "rdns" in the range [beg,end)) and, optionally 109 * (if "name" is not null), the unparsed DN. 110 */ 111 private LdapName(String name, Vector<Rdn> rdns, int beg, int end) { 112 unparsed = name; 113 this.rdns = new Vector<>(); 114 for (int i = beg; i < end; i++) { 115 this.rdns.addElement(rdns.elementAt(i)); 116 } 117 } 118 119 120 public Object clone() { 121 return new LdapName(unparsed, rdns); 122 } 123 124 public String toString() { 125 if (unparsed != null) { 126 return unparsed; 127 } 128 129 StringBuffer buf = new StringBuffer(); 130 for (int i = rdns.size() - 1; i >= 0; i--) { 131 if (i < rdns.size() - 1) { 132 buf.append(','); 133 } 134 Rdn rdn = rdns.elementAt(i); 135 buf.append(rdn); 136 } 137 138 unparsed = new String(buf); 139 return unparsed; 140 } 141 142 public boolean equals(Object obj) { 143 return ((obj instanceof LdapName) && 144 (compareTo(obj) == 0)); 145 } 146 147 public int compareTo(Object obj) { 148 LdapName that = (LdapName)obj; 149 150 if ((obj == this) || // check possible shortcuts 151 (unparsed != null && unparsed.equals(that.unparsed))) { 152 return 0; 153 } 154 155 // Compare RDNs one by one, lexicographically. 156 int minSize = Math.min(rdns.size(), that.rdns.size()); 157 for (int i = 0 ; i < minSize; i++) { 158 // Compare a single pair of RDNs. 159 Rdn rdn1 = rdns.elementAt(i); 160 Rdn rdn2 = that.rdns.elementAt(i); 161 162 int diff = rdn1.compareTo(rdn2); 163 if (diff != 0) { 164 return diff; 165 } 166 } 167 return (rdns.size() - that.rdns.size()); // longer DN wins 168 } 169 170 public int hashCode() { 171 // Sum up the hash codes of the components. 172 int hash = 0; 173 174 // For each RDN... 175 for (int i = 0; i < rdns.size(); i++) { 176 Rdn rdn = rdns.elementAt(i); 177 hash += rdn.hashCode(); 178 } 179 return hash; 180 } 181 182 public int size() { 183 return rdns.size(); 184 } 185 186 public boolean isEmpty() { 187 return rdns.isEmpty(); 188 } 189 190 public Enumeration<String> getAll() { 191 final Enumeration<Rdn> enum_ = rdns.elements(); 192 193 return new Enumeration<String>() { 194 public boolean hasMoreElements() { 195 return enum_.hasMoreElements(); 196 } 197 public String nextElement() { 198 return enum_.nextElement().toString(); 199 } 200 }; 201 } 202 203 public String get(int pos) { 204 return rdns.elementAt(pos).toString(); 205 } 206 207 public Name getPrefix(int pos) { 208 return new LdapName(null, rdns, 0, pos); 209 } 210 211 public Name getSuffix(int pos) { 212 return new LdapName(null, rdns, pos, rdns.size()); 213 } 214 215 public boolean startsWith(Name n) { 216 int len1 = rdns.size(); 217 int len2 = n.size(); 238 parse(); 239 } catch (InvalidNameException e) { 240 // shouldn't happen 241 throw new IllegalStateException("Cannot parse name: " + unparsed); 242 } 243 valuesCaseSensitive = caseSensitive; 244 } 245 246 /* 247 * Helper method for startsWith() and endsWith(). 248 * Returns true if components [beg,end) match the components of "n". 249 * If "n" is not an LdapName, each of its components is parsed as 250 * the string form of an RDN. 251 * The following must hold: end - beg == n.size(). 252 */ 253 private boolean matches(int beg, int end, Name n) { 254 for (int i = beg; i < end; i++) { 255 Rdn rdn; 256 if (n instanceof LdapName) { 257 LdapName ln = (LdapName)n; 258 rdn = ln.rdns.elementAt(i - beg); 259 } else { 260 String rdnString = n.get(i - beg); 261 try { 262 rdn = (new DnParser(rdnString, valuesCaseSensitive)).getRdn(); 263 } catch (InvalidNameException e) { 264 return false; 265 } 266 } 267 268 if (!rdn.equals(rdns.elementAt(i))) { 269 return false; 270 } 271 } 272 return true; 273 } 274 275 public Name addAll(Name suffix) throws InvalidNameException { 276 return addAll(size(), suffix); 277 } 278 279 /* 280 * If "suffix" is not an LdapName, each of its components is parsed as 281 * the string form of an RDN. 282 */ 283 public Name addAll(int pos, Name suffix) throws InvalidNameException { 284 if (suffix instanceof LdapName) { 285 LdapName s = (LdapName)suffix; 286 for (int i = 0; i < s.rdns.size(); i++) { 287 rdns.insertElementAt(s.rdns.elementAt(i), pos++); 288 } 289 } else { 290 Enumeration<String> comps = suffix.getAll(); 291 while (comps.hasMoreElements()) { 292 DnParser p = new DnParser(comps.nextElement(), 293 valuesCaseSensitive); 294 rdns.insertElementAt(p.getRdn(), pos++); 295 } 296 } 297 unparsed = null; // no longer valid 298 return this; 299 } 300 301 public Name add(String comp) throws InvalidNameException { 302 return add(size(), comp); 303 } 304 305 public Name add(int pos, String comp) throws InvalidNameException { 306 Rdn rdn = (new DnParser(comp, valuesCaseSensitive)).getRdn(); 307 rdns.insertElementAt(rdn, pos); 308 unparsed = null; // no longer valid 309 return this; 310 } 311 312 public Object remove(int pos) throws InvalidNameException { 390 private final String name; // DN being parsed 391 private final char[] chars; // characters in LDAP name being parsed 392 private final int len; // length of "chars" 393 private int cur = 0; // index of first unconsumed char in "chars" 394 private boolean valuesCaseSensitive; 395 396 /* 397 * Given an LDAP DN in string form, returns a parser for it. 398 */ 399 DnParser(String name, boolean valuesCaseSensitive) 400 throws InvalidNameException { 401 this.name = name; 402 len = name.length(); 403 chars = name.toCharArray(); 404 this.valuesCaseSensitive = valuesCaseSensitive; 405 } 406 407 /* 408 * Parses the DN, returning a Vector of its RDNs. 409 */ 410 Vector<Rdn> getDn() throws InvalidNameException { 411 cur = 0; 412 Vector<Rdn> rdns = new Vector<>(len / 3 + 10); // leave room for growth 413 414 if (len == 0) { 415 return rdns; 416 } 417 418 rdns.addElement(parseRdn()); 419 while (cur < len) { 420 if (chars[cur] == ',' || chars[cur] == ';') { 421 ++cur; 422 rdns.insertElementAt(parseRdn(), 0); 423 } else { 424 throw new InvalidNameException("Invalid name: " + name); 425 } 426 } 427 return rdns; 428 } 429 430 /* 431 * Parses the DN, if it is known to contain a single RDN. 432 */ 579 * a string attribute value. 580 */ 581 private boolean atTerminator() { 582 return (cur < len && 583 (chars[cur] == ',' || 584 chars[cur] == ';' || 585 chars[cur] == '+')); 586 } 587 } 588 589 590 /* 591 * Class Rdn represents a set of TypeAndValue. 592 */ 593 static class Rdn { 594 595 /* 596 * A vector of the TypeAndValue elements of this Rdn. 597 * It is sorted to facilitate set operations. 598 */ 599 private final Vector<TypeAndValue> tvs = new Vector<>(); 600 601 void add(TypeAndValue tv) { 602 603 // Set i to index of first element greater than tv, or to 604 // tvs.size() if there is none. 605 int i; 606 for (i = 0; i < tvs.size(); i++) { 607 int diff = tv.compareTo(tvs.elementAt(i)); 608 if (diff == 0) { 609 return; // tv is a duplicate: ignore it 610 } else if (diff < 0) { 611 break; 612 } 613 } 614 615 tvs.insertElementAt(tv, i); 616 } 617 618 public String toString() { 619 StringBuffer buf = new StringBuffer(); 620 for (int i = 0; i < tvs.size(); i++) { 621 if (i > 0) { 622 buf.append('+'); 623 } 624 buf.append(tvs.elementAt(i)); 625 } 626 return new String(buf); 627 } 628 629 public boolean equals(Object obj) { 630 return ((obj instanceof Rdn) && 631 (compareTo(obj) == 0)); 632 } 633 634 // Compare TypeAndValue components one by one, lexicographically. 635 public int compareTo(Object obj) { 636 Rdn that = (Rdn)obj; 637 int minSize = Math.min(tvs.size(), that.tvs.size()); 638 for (int i = 0; i < minSize; i++) { 639 // Compare a single pair of type/value pairs. 640 TypeAndValue tv = tvs.elementAt(i); 641 int diff = tv.compareTo(that.tvs.elementAt(i)); 642 if (diff != 0) { 643 return diff; 644 } 645 } 646 return (tvs.size() - that.tvs.size()); // longer RDN wins 647 } 648 649 public int hashCode() { 650 // Sum up the hash codes of the components. 651 int hash = 0; 652 653 // For each type/value pair... 654 for (int i = 0; i < tvs.size(); i++) { 655 hash += tvs.elementAt(i).hashCode(); 656 } 657 return hash; 658 } 659 660 Attributes toAttributes() { 661 Attributes attrs = new BasicAttributes(true); 662 TypeAndValue tv; 663 Attribute attr; 664 665 for (int i = 0; i < tvs.size(); i++) { 666 tv = tvs.elementAt(i); 667 if ((attr = attrs.get(tv.getType())) == null) { 668 attrs.put(tv.getType(), tv.getUnescapedValue()); 669 } else { 670 attr.add(tv.getUnescapedValue()); 671 } 672 } 673 return attrs; 674 } 675 } 676 677 678 /* 679 * Class TypeAndValue represents an attribute type and its 680 * corresponding value. 681 */ 682 static class TypeAndValue { 683 684 private final String type; 685 private final String value; // value, escaped or quoted 686 private final boolean binary; |