package com.hwlcn.ldap.ldap.sdk; import java.io.Serializable; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import com.hwlcn.ldap.asn1.ASN1OctetString; import com.hwlcn.ldap.ldap.sdk.schema.Schema; import com.hwlcn.core.annotation.NotMutable; import com.hwlcn.core.annotation.ThreadSafety; import com.hwlcn.ldap.util.ThreadSafetyLevel; import static com.hwlcn.ldap.ldap.sdk.LDAPMessages.*; import static com.hwlcn.ldap.util.Validator.*; @NotMutable() @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) public final class DN implements Comparable<DN>, Comparator<DN>, Serializable { private static final RDN[] NO_RDNS = new RDN[0]; public static final DN NULL_DN = new DN(); private static final long serialVersionUID = -5272968942085729346L; private final RDN[] rdns; private final Schema schema; private final String dnString; private volatile String normalizedString; public DN(final RDN... rdns) { ensureNotNull(rdns); this.rdns = rdns; if (rdns.length == 0) { dnString = ""; normalizedString = ""; schema = null; } else { Schema s = null; final StringBuilder buffer = new StringBuilder(); for (final RDN rdn : rdns) { if (buffer.length() > 0) { buffer.append(','); } rdn.toString(buffer, false); if (s == null) { s = rdn.getSchema(); } } dnString = buffer.toString(); schema = s; } } public DN(final List<RDN> rdns) { ensureNotNull(rdns); if (rdns.isEmpty()) { this.rdns = NO_RDNS; dnString = ""; normalizedString = ""; schema = null; } else { this.rdns = rdns.toArray(new RDN[rdns.size()]); Schema s = null; final StringBuilder buffer = new StringBuilder(); for (final RDN rdn : this.rdns) { if (buffer.length() > 0) { buffer.append(','); } rdn.toString(buffer, false); if (s == null) { s = rdn.getSchema(); } } dnString = buffer.toString(); schema = s; } } public DN(final RDN rdn, final DN parentDN) { ensureNotNull(rdn, parentDN); rdns = new RDN[parentDN.rdns.length + 1]; rdns[0] = rdn; System.arraycopy(parentDN.rdns, 0, rdns, 1, parentDN.rdns.length); Schema s = null; final StringBuilder buffer = new StringBuilder(); for (final RDN r : rdns) { if (buffer.length() > 0) { buffer.append(','); } r.toString(buffer, false); if (s == null) { s = r.getSchema(); } } dnString = buffer.toString(); schema = s; } public DN(final String dnString) throws LDAPException { this(dnString, null); } public DN(final String dnString, final Schema schema) throws LDAPException { ensureNotNull(dnString); this.dnString = dnString; this.schema = schema; final ArrayList<RDN> rdnList = new ArrayList<RDN>(5); final int length = dnString.length(); if (length == 0) { rdns = NO_RDNS; normalizedString = ""; return; } int pos = 0; boolean expectMore = false; rdnLoop: while (pos < length) { while ((pos < length) && (dnString.charAt(pos) == ' ')) { pos++; } if (pos >= length) { if (rdnList.isEmpty()) { break; } else { throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, ERR_DN_ENDS_WITH_COMMA.get()); } } int rdnEndPos; int rdnStartPos = pos; int attrStartPos = pos; while (pos < length) { final char c = dnString.charAt(pos); if ((c == ' ') || (c == '=')) { break; } else if ((c == ',') || (c == ';')) { throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, ERR_DN_UNEXPECTED_COMMA.get(pos)); } pos++; } String attrName = dnString.substring(attrStartPos, pos); if (attrName.length() == 0) { throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, ERR_DN_NO_ATTR_IN_RDN.get()); } while ((pos < length) && (dnString.charAt(pos) == ' ')) { pos++; } if ((pos >= length) || (dnString.charAt(pos) != '=')) { throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, ERR_DN_NO_EQUAL_SIGN.get(attrName)); } pos++; while ((pos < length) && (dnString.charAt(pos) == ' ')) { pos++; } if (pos >= length) { throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, ERR_DN_NO_VALUE_FOR_ATTR.get(attrName)); } ASN1OctetString value; if (dnString.charAt(pos) == '#') { final byte[] valueArray = RDN.readHexString(dnString, ++pos); value = new ASN1OctetString(valueArray); pos += (valueArray.length * 2); rdnEndPos = pos; } else { final StringBuilder buffer = new StringBuilder(); pos = RDN.readValueString(dnString, pos, buffer); value = new ASN1OctetString(buffer.toString()); rdnEndPos = pos; } while ((pos < length) && (dnString.charAt(pos) == ' ')) { pos++; } if (pos >= length) { rdnList.add(new RDN(attrName, value, schema, getTrimmedRDN(dnString, rdnStartPos,rdnEndPos))); expectMore = false; break; } switch (dnString.charAt(pos)) { case '+': pos++; break; case ',': case ';': rdnList.add(new RDN(attrName, value, schema, getTrimmedRDN(dnString, rdnStartPos,rdnEndPos))); pos++; expectMore = true; continue rdnLoop; default: throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, ERR_DN_UNEXPECTED_CHAR.get( dnString.charAt(pos), pos)); } if (pos >= length) { throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, ERR_DN_ENDS_WITH_PLUS.get()); } final ArrayList<String> nameList = new ArrayList<String>(5); final ArrayList<ASN1OctetString> valueList = new ArrayList<ASN1OctetString>(5); nameList.add(attrName); valueList.add(value); while (pos < length) { while ((pos < length) && (dnString.charAt(pos) == ' ')) { pos++; } if (pos >= length) { throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, ERR_DN_ENDS_WITH_PLUS.get()); } attrStartPos = pos; while (pos < length) { final char c = dnString.charAt(pos); if ((c == ' ') || (c == '=')) { break; } else if ((c == ',') || (c == ';')) { throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, ERR_DN_UNEXPECTED_COMMA.get(pos)); } pos++; } attrName = dnString.substring(attrStartPos, pos); if (attrName.length() == 0) { throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, ERR_DN_NO_ATTR_IN_RDN.get()); } while ((pos < length) && (dnString.charAt(pos) == ' ')) { pos++; } if ((pos >= length) || (dnString.charAt(pos) != '=')) { throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, ERR_DN_NO_EQUAL_SIGN.get(attrName)); } pos++; while ((pos < length) && (dnString.charAt(pos) == ' ')) { pos++; } if (pos >= length) { throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, ERR_DN_NO_VALUE_FOR_ATTR.get(attrName)); } if (dnString.charAt(pos) == '#') { final byte[] valueArray = RDN.readHexString(dnString, ++pos); value = new ASN1OctetString(valueArray); pos += (valueArray.length * 2); rdnEndPos = pos; } else { final StringBuilder buffer = new StringBuilder(); pos = RDN.readValueString(dnString, pos, buffer); value = new ASN1OctetString(buffer.toString()); rdnEndPos = pos; } while ((pos < length) && (dnString.charAt(pos) == ' ')) { pos++; } nameList.add(attrName); valueList.add(value); if (pos >= length) { final String[] names = nameList.toArray(new String[nameList.size()]); final ASN1OctetString[] values = valueList.toArray(new ASN1OctetString[valueList.size()]); rdnList.add(new RDN(names, values, schema, getTrimmedRDN(dnString, rdnStartPos,rdnEndPos))); expectMore = false; break rdnLoop; } switch (dnString.charAt(pos)) { case '+': pos++; if (pos >= length) { throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, ERR_DN_ENDS_WITH_PLUS.get()); } break; case ',': case ';': final String[] names = nameList.toArray(new String[nameList.size()]); final ASN1OctetString[] values = valueList.toArray(new ASN1OctetString[valueList.size()]); rdnList.add(new RDN(names, values, schema, getTrimmedRDN(dnString, rdnStartPos,rdnEndPos))); pos++; expectMore = true; continue rdnLoop; default: throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, ERR_DN_UNEXPECTED_CHAR.get( dnString.charAt(pos), pos)); } } } if (expectMore) { throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, ERR_DN_ENDS_WITH_COMMA.get()); } rdns = new RDN[rdnList.size()]; rdnList.toArray(rdns); } private static String getTrimmedRDN(final String dnString, final int start, final int end) { final String rdnString = dnString.substring(start, end); if (! rdnString.endsWith(" ")) { return rdnString; } final StringBuilder buffer = new StringBuilder(rdnString); while ((buffer.charAt(buffer.length() - 1) == ' ') && (buffer.charAt(buffer.length() - 2) != '\\')) { buffer.setLength(buffer.length() - 1); } return buffer.toString(); } public static boolean isValidDN(final String s) { try { new DN(s); return true; } catch (LDAPException le) { return false; } } public RDN getRDN() { if (rdns.length == 0) { return null; } else { return rdns[0]; } } public String getRDNString() { if (rdns.length == 0) { return null; } else { return rdns[0].toString(); } } public static String getRDNString(final String s) throws LDAPException { return new DN(s).getRDNString(); } public RDN[] getRDNs() { return rdns; } public static RDN[] getRDNs(final String s) throws LDAPException { return new DN(s).getRDNs(); } public String[] getRDNStrings() { final String[] rdnStrings = new String[rdns.length]; for (int i=0; i < rdns.length; i++) { rdnStrings[i] = rdns[i].toString(); } return rdnStrings; } public static String[] getRDNStrings(final String s) throws LDAPException { return new DN(s).getRDNStrings(); } public boolean isNullDN() { return (rdns.length == 0); } public DN getParent() { switch (rdns.length) { case 0: case 1: return null; case 2: return new DN(rdns[1]); case 3: return new DN(rdns[1], rdns[2]); case 4: return new DN(rdns[1], rdns[2], rdns[3]); case 5: return new DN(rdns[1], rdns[2], rdns[3], rdns[4]); default: final RDN[] parentRDNs = new RDN[rdns.length - 1]; System.arraycopy(rdns, 1, parentRDNs, 0, parentRDNs.length); return new DN(parentRDNs); } } public static DN getParent(final String s) throws LDAPException { return new DN(s).getParent(); } public String getParentString() { final DN parentDN = getParent(); if (parentDN == null) { return null; } else { return parentDN.toString(); } } public static String getParentString(final String s) throws LDAPException { return new DN(s).getParentString(); } public boolean isAncestorOf(final DN dn, final boolean allowEquals) { int thisPos = rdns.length - 1; int thatPos = dn.rdns.length - 1; if (thisPos < 0) { return (allowEquals || (thatPos >= 0)); } if ((thisPos > thatPos) || ((thisPos == thatPos) && (! allowEquals))) { return false; } while (thisPos >= 0) { if (! rdns[thisPos--].equals(dn.rdns[thatPos--])) { return false; } } return true; } public boolean isAncestorOf(final String s, final boolean allowEquals) throws LDAPException { return isAncestorOf(new DN(s), allowEquals); } public static boolean isAncestorOf(final String s1, final String s2, final boolean allowEquals) throws LDAPException { return new DN(s1).isAncestorOf(new DN(s2), allowEquals); } public boolean isDescendantOf(final DN dn, final boolean allowEquals) { int thisPos = rdns.length - 1; int thatPos = dn.rdns.length - 1; if (thatPos < 0) { return (allowEquals || (thisPos >= 0)); } if ((thisPos < thatPos) || ((thisPos == thatPos) && (! allowEquals))) { return false; } while (thatPos >= 0) { if (! rdns[thisPos--].equals(dn.rdns[thatPos--])) { return false; } } return true; } public boolean isDescendantOf(final String s, final boolean allowEquals) throws LDAPException { return isDescendantOf(new DN(s), allowEquals); } public static boolean isDescendantOf(final String s1, final String s2, final boolean allowEquals) throws LDAPException { return new DN(s1).isDescendantOf(new DN(s2), allowEquals); } public boolean matchesBaseAndScope(final String baseDN, final SearchScope scope) throws LDAPException { return matchesBaseAndScope(new DN(baseDN), scope); } public boolean matchesBaseAndScope(final DN baseDN, final SearchScope scope) throws LDAPException { ensureNotNull(baseDN, scope); switch (scope.intValue()) { case SearchScope.BASE_INT_VALUE: return equals(baseDN); case SearchScope.ONE_INT_VALUE: return baseDN.equals(getParent()); case SearchScope.SUB_INT_VALUE: return isDescendantOf(baseDN, true); case SearchScope.SUBORDINATE_SUBTREE_INT_VALUE: return isDescendantOf(baseDN, false); default: throw new LDAPException(ResultCode.PARAM_ERROR, ERR_DN_MATCHES_UNSUPPORTED_SCOPE.get(dnString, String.valueOf(scope))); } } @Override() public int hashCode() { return toNormalizedString().hashCode(); } @Override() public boolean equals(final Object o) { if (o == null) { return false; } if (this == o) { return true; } if (! (o instanceof DN)) { return false; } final DN dn = (DN) o; return (toNormalizedString().equals(dn.toNormalizedString())); } public boolean equals(final String s) throws LDAPException { if (s == null) { return false; } return equals(new DN(s)); } public static boolean equals(final String s1, final String s2) throws LDAPException { return new DN(s1).equals(new DN(s2)); } @Override() public String toString() { return dnString; } public String toMinimallyEncodedString() { final StringBuilder buffer = new StringBuilder(); toString(buffer, true); return buffer.toString(); } public void toString(final StringBuilder buffer) { toString(buffer, false); } public void toString(final StringBuilder buffer, final boolean minimizeEncoding) { for (int i=0; i < rdns.length; i++) { if (i > 0) { buffer.append(','); } rdns[i].toString(buffer, minimizeEncoding); } } public String toNormalizedString() { if (normalizedString == null) { final StringBuilder buffer = new StringBuilder(); toNormalizedString(buffer); normalizedString = buffer.toString(); } return normalizedString; } public void toNormalizedString(final StringBuilder buffer) { for (int i=0; i < rdns.length; i++) { if (i > 0) { buffer.append(','); } buffer.append(rdns[i].toNormalizedString()); } } public static String normalize(final String s) throws LDAPException { return normalize(s, null); } public static String normalize(final String s, final Schema schema) throws LDAPException { return new DN(s, schema).toNormalizedString(); } public int compareTo(final DN dn) { return compare(this, dn); } public int compare(final DN dn1, final DN dn2) { ensureNotNull(dn1, dn2); int pos1 = dn1.rdns.length - 1; int pos2 = dn2.rdns.length - 1; if (pos1 < 0) { if (pos2 < 0) { return 0; } else { return -1; } } else if (pos2 < 0) { return 1; } while ((pos1 >= 0) && (pos2 >= 0)) { final int compValue = dn1.rdns[pos1].compareTo(dn2.rdns[pos2]); if (compValue != 0) { return compValue; } pos1--; pos2--; } if (pos1 < 0) { if (pos2 < 0) { return 0; } else { return -1; } } else { return 1; } } public static int compare(final String s1, final String s2) throws LDAPException { return compare(s1, s2, null); } public static int compare(final String s1, final String s2, final Schema schema) throws LDAPException { return new DN(s1, schema).compareTo(new DN(s2, schema)); } }