/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2010 Sun Microsystems, Inc. */ package org.opends.server.types; import org.opends.messages.Message; import java.util.Iterator; import java.util.List; import java.util.TreeMap; import java.util.TreeSet; import org.opends.server.api.OrderingMatchingRule; import org.opends.server.core.DirectoryServer; import static org.opends.server.loggers.debug.DebugLogger.*; import org.opends.server.loggers.debug.DebugTracer; import static org.opends.messages.CoreMessages.*; import static org.opends.server.util.StaticUtils.*; /** * This class defines a data structure for storing and interacting * with the relative distinguished names associated with entries in * the Directory Server. */ @org.opends.server.types.PublicAPI( stability=org.opends.server.types.StabilityLevel.UNCOMMITTED, mayInstantiate=true, mayExtend=false, mayInvoke=true) public final class RDN implements Comparable<RDN> { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); // The set of attribute types for the elements in this RDN. private AttributeType[] attributeTypes; // The set of values for the elements in this RDN. private AttributeValue[] attributeValues; // The number of values for this RDN. private int numValues; // The string representation of the normalized form of this RDN. private String normalizedRDN; // The string representation of this RDN. private String rdnString; // The set of user-provided names for the attributes in this RDN. private String[] attributeNames; /** * Creates a new RDN with the provided information. * * @param attributeType The attribute type for this RDN. It must * not be {@code null}. * @param attributeValue The value for this RDN. It must not be * {@code null}. */ public RDN(AttributeType attributeType, AttributeValue attributeValue) { attributeTypes = new AttributeType[] { attributeType }; attributeNames = new String[] { attributeType.getPrimaryName() }; attributeValues = new AttributeValue[] { attributeValue }; numValues = 1; rdnString = null; normalizedRDN = null; } /** * Creates a new RDN with the provided information. * * @param attributeType The attribute type for this RDN. It must * not be {@code null}. * @param attributeName The user-provided name for this RDN. It * must not be {@code null}. * @param attributeValue The value for this RDN. It must not be * {@code null}. */ public RDN(AttributeType attributeType, String attributeName, AttributeValue attributeValue) { attributeTypes = new AttributeType[] { attributeType }; attributeNames = new String[] { attributeName }; attributeValues = new AttributeValue[] { attributeValue }; numValues = 1; rdnString = null; normalizedRDN = null; } /** * Creates a new RDN with the provided information. The number of * type, name, and value elements must be nonzero and equal. * * @param attributeTypes The set of attribute types for this RDN. * It must not be empty or {@code null}. * @param attributeNames The set of user-provided names for this * RDN. It must have the same number of * elements as the {@code attributeTypes} * argument. * @param attributeValues The set of values for this RDN. It must * have the same number of elements as the * {@code attributeTypes} argument. */ public RDN(List<AttributeType> attributeTypes, List<String> attributeNames, List<AttributeValue> attributeValues) { this.attributeTypes = new AttributeType[attributeTypes.size()]; this.attributeNames = new String[attributeNames.size()]; this.attributeValues = new AttributeValue[attributeValues.size()]; attributeTypes.toArray(this.attributeTypes); attributeNames.toArray(this.attributeNames); attributeValues.toArray(this.attributeValues); numValues = attributeTypes.size(); rdnString = null; normalizedRDN = null; } /** * Creates a new RDN with the provided information. The number of * type, name, and value elements must be nonzero and equal. * * @param attributeTypes The set of attribute types for this RDN. * It must not be empty or {@code null}. * @param attributeNames The set of user-provided names for this * RDN. It must have the same number of * elements as the {@code attributeTypes} * argument. * @param attributeValues The set of values for this RDN. It must * have the same number of elements as the * {@code attributeTypes} argument. */ public RDN(AttributeType[] attributeTypes, String[] attributeNames, AttributeValue[] attributeValues) { this.numValues = attributeTypes.length; this.attributeTypes = attributeTypes; this.attributeNames = attributeNames; this.attributeValues = attributeValues; rdnString = null; normalizedRDN = null; } /** * Creates a new RDN with the provided information. * * @param attributeType The attribute type for this RDN. It must * not be {@code null}. * @param attributeValue The value for this RDN. It must not be * {@code null}. * * @return The RDN created with the provided information. */ public static RDN create(AttributeType attributeType, AttributeValue attributeValue) { return new RDN(attributeType, attributeValue); } /** * Retrieves the number of attribute-value pairs contained in this * RDN. * * @return The number of attribute-value pairs contained in this * RDN. */ public int getNumValues() { return numValues; } /** * Indicates whether this RDN includes the specified attribute type. * * @param attributeType The attribute type for which to make the * determination. * * @return <CODE>true</CODE> if the RDN includes the specified * attribute type, or <CODE>false</CODE> if not. */ public boolean hasAttributeType(AttributeType attributeType) { for (AttributeType t : attributeTypes) { if (t.equals(attributeType)) { return true; } } return false; } /** * Indicates whether this RDN includes the specified attribute type. * * @param lowerName The name or OID for the attribute type for * which to make the determination, formatted in * all lowercase characters. * * @return <CODE>true</CODE> if the RDN includes the specified * attribute type, or <CODE>false</CODE> if not. */ public boolean hasAttributeType(String lowerName) { for (AttributeType t : attributeTypes) { if (t.hasNameOrOID(lowerName)) { return true; } } for (String s : attributeNames) { if (s.equalsIgnoreCase(lowerName)) { return true; } } return false; } /** * Retrieves the attribute type at the specified position in the set * of attribute types for this RDN. * * @param pos The position of the attribute type to retrieve. * * @return The attribute type at the specified position in the set * of attribute types for this RDN. */ public AttributeType getAttributeType(int pos) { return attributeTypes[pos]; } /** * Retrieves the name for the attribute type at the specified * position in the set of attribute types for this RDN. * * @param pos The position of the attribute type for which to * retrieve the name. * * @return The name for the attribute type at the specified * position in the set of attribute types for this RDN. */ public String getAttributeName(int pos) { return attributeNames[pos]; } /** * Retrieves the attribute value that is associated with the * specified attribute type. * * @param attributeType The attribute type for which to retrieve * the corresponding value. * * @return The value for the requested attribute type, or * <CODE>null</CODE> if the specified attribute type is not * present in the RDN. */ public AttributeValue getAttributeValue(AttributeType attributeType) { for (int i=0; i < numValues; i++) { if (attributeTypes[i].equals(attributeType)) { return attributeValues[i]; } } return null; } /** * Retrieves the value for the attribute type at the specified * position in the set of attribute types for this RDN. * * @param pos The position of the attribute type for which to * retrieve the value. * * @return The value for the attribute type at the specified * position in the set of attribute types for this RDN. */ public AttributeValue getAttributeValue(int pos) { return attributeValues[pos]; } /** * Indicates whether this RDN is multivalued. * * @return <CODE>true</CODE> if this RDN is multivalued, or * <CODE>false</CODE> if not. */ public boolean isMultiValued() { return (numValues > 1); } /** * Indicates whether this RDN contains the specified type-value * pair. * * @param type The attribute type for which to make the * determination. * @param value The value for which to make the determination. * * @return <CODE>true</CODE> if this RDN contains the specified * attribute value, or <CODE>false</CODE> if not. */ public boolean hasValue(AttributeType type, AttributeValue value) { for (int i=0; i < numValues; i++) { if (attributeTypes[i].equals(type) && attributeValues[i].equals(value)) { return true; } } return false; } /** * Adds the provided type-value pair from this RDN. Note that this * is intended only for internal use when constructing DN values. * * @param type The attribute type of the pair to add. * @param name The user-provided name of the pair to add. * @param value The attribute value of the pair to add. * * @return <CODE>true</CODE> if the type-value pair was added to * this RDN, or <CODE>false</CODE> if it was not (e.g., it * was already present). */ boolean addValue(AttributeType type, String name, AttributeValue value) { for (int i=0; i < numValues; i++) { if (attributeTypes[i].equals(type) && attributeValues[i].equals(value)) { return false; } } numValues++; AttributeType[] newTypes = new AttributeType[numValues]; System.arraycopy(attributeTypes, 0, newTypes, 0, attributeTypes.length); newTypes[attributeTypes.length] = type; attributeTypes = newTypes; String[] newNames = new String[numValues]; System.arraycopy(attributeNames, 0, newNames, 0, attributeNames.length); newNames[attributeNames.length] = name; attributeNames = newNames; AttributeValue[] newValues = new AttributeValue[numValues]; System.arraycopy(attributeValues, 0, newValues, 0, attributeValues.length); newValues[attributeValues.length] = value; attributeValues = newValues; rdnString = null; normalizedRDN = null; return true; } /** * Retrieves a version of the provided value in a form that is * properly escaped for use in a DN or RDN. * * @param value The value to be represented in a DN-safe form. * * @return A version of the provided value in a form that is * properly escaped for use in a DN or RDN. */ private static String getDNValue(String value) { if ((value == null) || (value.length() == 0)) { return ""; } // Only copy the string value if required. boolean needsEscaping = false; int length = value.length(); needsEscaping: { char c = value.charAt(0); if ((c == ' ') || (c == '#')) { needsEscaping = true; break needsEscaping; } if (value.charAt(length - 1) == ' ') { needsEscaping = true; break needsEscaping; } for (int i = 0; i < length; i++) { c = value.charAt(i); if (c < ' ') { needsEscaping = true; break needsEscaping; } else { switch (c) { case ',': case '+': case '"': case '\\': case '<': case '>': case ';': needsEscaping = true; break needsEscaping; } } } } if (!needsEscaping) { return value; } // We need to copy and escape the string (allow for at least one // escaped character). StringBuilder buffer = new StringBuilder(length + 3); // If the lead character is a space or a # it must be escaped. int start = 0; char c = value.charAt(0); if ((c == ' ') || (c == '#')) { buffer.append('\\'); buffer.append(c); start = 1; } // Escape remaining characters as necessary. for (int i = start; i < length; i++) { c = value.charAt(i); if (c < ' ') { for (byte b : getBytes(String.valueOf(c))) { buffer.append('\\'); buffer.append(byteToLowerHex(b)); } } else { switch (value.charAt(i)) { case ',': case '+': case '"': case '\\': case '<': case '>': case ';': buffer.append('\\'); buffer.append(c); break; default: buffer.append(c); break; } } } // If the last character is a space it must be escaped. if (value.charAt(length - 1) == ' ') { length = buffer.length(); buffer.insert(length - 1, '\\'); } return buffer.toString(); } /** * Decodes the provided string as an RDN. * * @param rdnString * The string to decode as an RDN. * @return The decoded RDN. * @throws DirectoryException * If a problem occurs while trying to decode the provided * string as a RDN. */ public static RDN decode(String rdnString) throws DirectoryException { // A null or empty RDN is not acceptable. if (rdnString == null) { Message message = ERR_RDN_DECODE_NULL.get(); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message); } int length = rdnString.length(); if (length == 0) { Message message = ERR_RDN_DECODE_NULL.get(); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message); } // Iterate through the RDN string. The first thing to do is to // get rid of any leading spaces. int pos = 0; char c = rdnString.charAt(pos); while (c == ' ') { pos++; if (pos == length) { // This means that the RDN was completely comprised of spaces, // which is not valid. Message message = ERR_RDN_DECODE_NULL.get(); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message); } else { c = rdnString.charAt(pos); } } // We know that it's not an empty RDN, so we can do the real // processing. First, parse the attribute name. We can borrow // the DN code for this. boolean allowExceptions = DirectoryServer.allowAttributeNameExceptions(); StringBuilder attributeName = new StringBuilder(); pos = DN.parseAttributeName(rdnString, pos, attributeName, allowExceptions); // Make sure that we're not at the end of the RDN string because // that would be invalid. if (pos >= length) { Message message = ERR_RDN_END_WITH_ATTR_NAME.get( rdnString, attributeName.toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message); } // Skip over any spaces between the attribute name and its value. c = rdnString.charAt(pos); while (c == ' ') { pos++; if (pos >= length) { // This means that we hit the end of the string before // finding a '='. This is illegal because there is no // attribute-value separator. Message message = ERR_RDN_END_WITH_ATTR_NAME.get( rdnString, attributeName.toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message); } else { c = rdnString.charAt(pos); } } // The next character must be an equal sign. If it is not, then // that's an error. if (c == '=') { pos++; } else { Message message = ERR_RDN_NO_EQUAL.get( rdnString, attributeName.toString(), c); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message); } // Skip over any spaces between the equal sign and the value. while ((pos < length) && ((c = rdnString.charAt(pos)) == ' ')) { pos++; } // If we are at the end of the RDN string, then that must mean // that the attribute value was empty. if (pos >= length) { String name = attributeName.toString(); String lowerName = toLowerCase(name); Message message = ERR_RDN_MISSING_ATTRIBUTE_VALUE.get(rdnString, lowerName); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message); } // Parse the value for this RDN component. This can be done using // the DN code. ByteStringBuilder parsedValue = new ByteStringBuilder(0); pos = DN.parseAttributeValue(rdnString, pos, parsedValue); // Create the new RDN with the provided information. However, // don't return it yet because this could be a multi-valued RDN. String name = attributeName.toString(); String lowerName = toLowerCase(name); AttributeType attrType = DirectoryServer.getAttributeType(lowerName); if (attrType == null) { // This must be an attribute type that we don't know about. // In that case, we'll create a new attribute using the default // syntax. If this is a problem, it will be caught later either // by not finding the target entry or by not allowing the entry // to be added. attrType = DirectoryServer.getDefaultAttributeType(name); } AttributeValue value = AttributeValues.create(attrType, parsedValue.toByteString()); RDN rdn = new RDN(attrType, name, value); // Skip over any spaces that might be after the attribute value. while ((pos < length) && ((c = rdnString.charAt(pos)) == ' ')) { pos++; } // Most likely, this is the end of the RDN. If so, then return // it. if (pos >= length) { return rdn; } // If the next character is a comma or semicolon, then that is not // allowed. It would be legal for a DN but not an RDN. if ((c == ',') || (c == ';')) { Message message = ERR_RDN_UNEXPECTED_COMMA.get(rdnString, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message); } // If the next character is anything but a plus sign, then it is // illegal. if (c != '+') { Message message = ERR_RDN_ILLEGAL_CHARACTER.get(rdnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message); } // If we have gotten here, then it is a multi-valued RDN. Parse // the remaining attribute/value pairs and add them to the RDN // that we've already created. while (true) { // Skip over the plus sign and any spaces that may follow it // before the next attribute name. pos++; while ((pos < length) && ((c = rdnString.charAt(pos)) == ' ')) { pos++; } // Parse the attribute name. attributeName = new StringBuilder(); pos = DN.parseAttributeName(rdnString, pos, attributeName, allowExceptions); // Make sure we're not at the end of the RDN. if (pos >= length) { Message message = ERR_RDN_END_WITH_ATTR_NAME.get( rdnString, attributeName.toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message); } // Skip over any spaces between the attribute name and the equal // sign. c = rdnString.charAt(pos); while (c == ' ') { pos++; if (pos >= length) { // This means that we hit the end of the string before // finding a '='. This is illegal because there is no // attribute-value separator. Message message = ERR_RDN_END_WITH_ATTR_NAME.get( rdnString, attributeName.toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message); } else { c = rdnString.charAt(pos); } } // The next character must be an equal sign. if (c == '=') { pos++; } else { Message message = ERR_RDN_NO_EQUAL.get( rdnString, attributeName.toString(), c); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message); } // Skip over any spaces after the equal sign. while ((pos < length) && ((c = rdnString.charAt(pos)) == ' ')) { pos++; } // If we are at the end of the RDN string, then that must mean // that the attribute value was empty. This will probably never // happen in a real-world environment, but technically isn't // illegal. If it does happen, then go ahead and return the // RDN. if (pos >= length) { name = attributeName.toString(); lowerName = toLowerCase(name); attrType = DirectoryServer.getAttributeType(lowerName); if (attrType == null) { // This must be an attribute type that we don't know about. // In that case, we'll create a new attribute using the // default syntax. If this is a problem, it will be caught // later either by not finding the target entry or by not // allowing the entry to be added. attrType = DirectoryServer.getDefaultAttributeType(name); } value = AttributeValues.create(ByteString.empty(), ByteString.empty()); rdn.addValue(attrType, name, value); return rdn; } // Parse the value for this RDN component. parsedValue.clear(); pos = DN.parseAttributeValue(rdnString, pos, parsedValue); // Update the RDN to include the new attribute/value. name = attributeName.toString(); lowerName = toLowerCase(name); attrType = DirectoryServer.getAttributeType(lowerName); if (attrType == null) { // This must be an attribute type that we don't know about. // In that case, we'll create a new attribute using the // default syntax. If this is a problem, it will be caught // later either by not finding the target entry or by not // allowing the entry to be added. attrType = DirectoryServer.getDefaultAttributeType(name); } value = AttributeValues.create(attrType, parsedValue.toByteString()); rdn.addValue(attrType, name, value); // Skip over any spaces that might be after the attribute value. while ((pos < length) && ((c = rdnString.charAt(pos)) == ' ')) { pos++; } // If we're at the end of the string, then return the RDN. if (pos >= length) { return rdn; } // If the next character is a comma or semicolon, then that is // not allowed. It would be legal for a DN but not an RDN. if ((c == ',') || (c == ';')) { Message message = ERR_RDN_UNEXPECTED_COMMA.get(rdnString, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message); } // If the next character is anything but a plus sign, then it is // illegal. if (c != '+') { Message message = ERR_RDN_ILLEGAL_CHARACTER.get(rdnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message); } } } /** * Creates a duplicate of this RDN that can be modified without * impacting this RDN. * * @return A duplicate of this RDN that can be modified without * impacting this RDN. */ public RDN duplicate() { AttributeType[] newTypes = new AttributeType[numValues]; System.arraycopy(attributeTypes, 0, newTypes, 0, numValues); String[] newNames = new String[numValues]; System.arraycopy(attributeNames, 0, newNames, 0, numValues); AttributeValue[] newValues = new AttributeValue[numValues]; System.arraycopy(attributeValues, 0, newValues, 0, numValues); return new RDN(newTypes, newNames, newValues); } /** * Indicates whether the provided object is equal to this RDN. It * will only be considered equal if it is an RDN object that * contains the same number of elements in the same order with the * same types and normalized values. * * @param o The object for which to make the determination. * * @return <CODE>true</CODE> if it is determined that the provided * object is equal to this RDN, or <CODE>false</CODE> if * not. */ public boolean equals(Object o) { if (this == o) { return true; } if ((o == null) || (! (o instanceof RDN))) { return false; } RDN rdn = (RDN) o; return toNormalizedString().equals(rdn.toNormalizedString()); } /** * Retrieves the hash code for this RDN. It will be calculated as * the sum of the hash codes of the types and values. * * @return The hash code for this RDN. */ public int hashCode() { return toNormalizedString().hashCode(); } /** * Retrieves a string representation of this RDN. * * @return A string representation of this RDN. */ public String toString() { if (rdnString == null) { StringBuilder buffer = new StringBuilder(); buffer.append(attributeNames[0]); buffer.append("="); String s = attributeValues[0].getValue().toString(); buffer.append(getDNValue(s)); for (int i=1; i < numValues; i++) { buffer.append("+"); buffer.append(attributeNames[i]); buffer.append("="); s = attributeValues[i].getValue().toString(); buffer.append(getDNValue(s)); } rdnString = buffer.toString(); } return rdnString; } /** * Appends a string representation of this RDN to the provided * buffer. * * @param buffer The buffer to which the string representation * should be appended. */ public void toString(StringBuilder buffer) { buffer.append(toString()); } /** * Retrieves a normalized string representation of this RDN. * * @return A normalized string representation of this RDN. */ public String toNormalizedString() { if (normalizedRDN == null) { StringBuilder buffer = new StringBuilder(); toNormalizedString(buffer); } return normalizedRDN; } /** * Appends a normalized string representation of this RDN to the * provided buffer. * * @param buffer The buffer to which to append the information. */ public void toNormalizedString(StringBuilder buffer) { if (normalizedRDN != null) { buffer.append(normalizedRDN); return; } boolean bufferEmpty = (buffer.length() == 0); if (attributeNames.length == 1) { getAVAString(0, buffer); } else { TreeSet<String> rdnElementStrings = new TreeSet<String>(); for (int i=0; i < attributeNames.length; i++) { StringBuilder b2 = new StringBuilder(); getAVAString(i, b2); rdnElementStrings.add(b2.toString()); } Iterator<String> iterator = rdnElementStrings.iterator(); buffer.append(iterator.next()); while (iterator.hasNext()) { buffer.append('+'); buffer.append(iterator.next()); } } if (bufferEmpty) { normalizedRDN = buffer.toString(); } } /** * Appends a normalized string representation of this RDN to the * provided buffer. * * @param pos The position of the attribute type and value to * retrieve. * @param buffer The buffer to which to append the information. */ public void getAVAString(int pos, StringBuilder buffer) { buffer.append( attributeTypes[pos].getNormalizedPrimaryNameOrOID()); buffer.append('='); try { String s = attributeValues[pos].getNormalizedValue().toString(); buffer.append(getDNValue(s)); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } String s = attributeValues[pos].getValue().toString(); buffer.append(getDNValue(s)); } } /** * Compares this RDN with the provided RDN based on an alphabetic * comparison of the attribute names and values. * * @param rdn The RDN against which to compare this RDN. * * @return A negative integer if this RDN should come before the * provided RDN, a positive integer if this RDN should come * after the provided RDN, or zero if there is no * difference with regard to ordering. */ public int compareTo(RDN rdn) { if ((attributeTypes.length == 1) && (rdn.attributeTypes.length == 1)) { if (attributeTypes[0].equals(rdn.attributeTypes[0])) { OrderingMatchingRule omr = attributeTypes[0].getOrderingMatchingRule(); if (omr == null) { try { return attributeValues[0].getNormalizedValue().toString(). compareTo(rdn.attributeValues[0]. getNormalizedValue().toString()); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } return attributeValues[0].getValue().toString(). compareTo(rdn.attributeValues[0]. getValue().toString()); } } else { try { return omr.compareValues( attributeValues[0].getNormalizedValue(), rdn.attributeValues[0].getNormalizedValue()); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } return omr.compareValues( attributeValues[0].getValue(), rdn.attributeValues[0].getValue()); } } } else { String name1 = attributeTypes[0].getNormalizedPrimaryNameOrOID(); String name2 = rdn.attributeTypes[0].getNormalizedPrimaryNameOrOID(); return name1.compareTo(name2); } } if (equals(rdn)) { return 0; } TreeMap<String,AttributeType> typeMap1 = new TreeMap<String,AttributeType>(); TreeMap<String,AttributeValue> valueMap1 = new TreeMap<String,AttributeValue>(); for (int i=0; i < attributeTypes.length; i++) { String lowerName = attributeTypes[i].getNormalizedPrimaryNameOrOID(); typeMap1.put(lowerName, attributeTypes[i]); valueMap1.put(lowerName, attributeValues[i]); } TreeMap<String,AttributeType> typeMap2 = new TreeMap<String,AttributeType>(); TreeMap<String,AttributeValue> valueMap2 = new TreeMap<String,AttributeValue>(); for (int i=0; i < rdn.attributeTypes.length; i++) { String lowerName = rdn.attributeTypes[i].getNormalizedPrimaryNameOrOID(); typeMap2.put(lowerName, rdn.attributeTypes[i]); valueMap2.put(lowerName, rdn.attributeValues[i]); } Iterator<String> iterator1 = valueMap1.keySet().iterator(); Iterator<String> iterator2 = valueMap2.keySet().iterator(); String name1 = iterator1.next(); String name2 = iterator2.next(); AttributeType type1 = typeMap1.get(name1); AttributeType type2 = typeMap2.get(name2); AttributeValue value1 = valueMap1.get(name1); AttributeValue value2 = valueMap2.get(name2); while (true) { if (type1.equals(type2)) { int valueComparison; OrderingMatchingRule omr = type1.getOrderingMatchingRule(); if (omr == null) { try { valueComparison = value1.getNormalizedValue().toString().compareTo( value2.getNormalizedValue().toString()); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } valueComparison = value1.getValue().toString().compareTo( value2.getValue().toString()); } } else { try { valueComparison = omr.compareValues(value1.getNormalizedValue(), value2.getNormalizedValue()); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } valueComparison = omr.compareValues(value1.getValue(), value2.getValue()); } } if (valueComparison == 0) { if (! iterator1.hasNext()) { if (iterator2.hasNext()) { return -1; } else { return 0; } } if (! iterator2.hasNext()) { return 1; } name1 = iterator1.next(); name2 = iterator2.next(); type1 = typeMap1.get(name1); type2 = typeMap2.get(name2); value1 = valueMap1.get(name1); value2 = valueMap2.get(name2); } else { return valueComparison; } } else { return name1.compareTo(name2); } } } }