Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2007-2017 UnboundID Corp.
* All Rights Reserved.
*/
/*
* Copyright (C) 2008-2017 UnboundID Corp.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
package com.unboundid.ldap.sdk;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
import com.unboundid.asn1.ASN1OctetString;
import com.unboundid.ldap.matchingrules.MatchingRule;
import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
import com.unboundid.ldap.sdk.schema.Schema;
import com.unboundid.util.NotMutable;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import static com.unboundid.ldap.sdk.LDAPMessages.*;
import static com.unboundid.util.Debug.*;
import static com.unboundid.util.StaticUtils.*;
import static com.unboundid.util.Validator.*;
/**
* This class provides a data structure for holding information about an LDAP
* relative distinguished name (RDN). An RDN consists of one or more
* attribute name-value pairs. See
* RFC 4514 for more
* information about representing DNs and RDNs as strings. See the
* documentation in the {@link DN} class for more information about DNs and
* RDNs.
*/
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class RDN
implements Comparable, Comparator, Serializable
{
/**
* The serial version UID for this serializable class.
*/
private static final long serialVersionUID = 2923419812807188487L;
// The set of attribute values for this RDN.
private final ASN1OctetString[] attributeValues;
// The schema to use to generate the normalized string representation of this
// RDN, if any.
private final Schema schema;
// The normalized string representation for this RDN.
private volatile String normalizedString;
// The user-defined string representation for this RDN.
private volatile String rdnString;
// The set of attribute names for this RDN.
private final String[] attributeNames;
/**
* Creates a new single-valued RDN with the provided information.
*
* @param attributeName The attribute name for this RDN. It must not be
* {@code null}.
* @param attributeValue The attribute value for this RDN. It must not be
* {@code null}.
*/
public RDN(final String attributeName, final String attributeValue)
{
this(attributeName, attributeValue, null);
}
/**
* Creates a new single-valued RDN with the provided information.
*
* @param attributeName The attribute name for this RDN. It must not be
* {@code null}.
* @param attributeValue The attribute value for this RDN. It must not be
* {@code null}.
* @param schema The schema to use to generate the normalized string
* representation of this RDN. It may be {@code null}
* if no schema is available.
*/
public RDN(final String attributeName, final String attributeValue,
final Schema schema)
{
ensureNotNull(attributeName, attributeValue);
this.schema = schema;
attributeNames = new String[] { attributeName };
attributeValues =
new ASN1OctetString[] { new ASN1OctetString(attributeValue) };
}
/**
* Creates a new single-valued RDN with the provided information.
*
* @param attributeName The attribute name for this RDN. It must not be
* {@code null}.
* @param attributeValue The attribute value for this RDN. It must not be
* {@code null}.
*/
public RDN(final String attributeName, final byte[] attributeValue)
{
this(attributeName, attributeValue, null);
}
/**
* Creates a new single-valued RDN with the provided information.
*
* @param attributeName The attribute name for this RDN. It must not be
* {@code null}.
* @param attributeValue The attribute value for this RDN. It must not be
* {@code null}.
* @param schema The schema to use to generate the normalized string
* representation of this RDN. It may be {@code null}
* if no schema is available.
*/
public RDN(final String attributeName, final byte[] attributeValue,
final Schema schema)
{
ensureNotNull(attributeName, attributeValue);
this.schema = schema;
attributeNames = new String[] { attributeName };
attributeValues =
new ASN1OctetString[] { new ASN1OctetString(attributeValue) };
}
/**
* Creates a new (potentially multivalued) RDN. The set of names must have
* the same number of elements as the set of values, and there must be at
* least one element in each array.
*
* @param attributeNames The set of attribute names for this RDN. It must
* not be {@code null} or empty.
* @param attributeValues The set of attribute values for this RDN. It must
* not be {@code null} or empty.
*/
public RDN(final String[] attributeNames, final String[] attributeValues)
{
this(attributeNames, attributeValues, null);
}
/**
* Creates a new (potentially multivalued) RDN. The set of names must have
* the same number of elements as the set of values, and there must be at
* least one element in each array.
*
* @param attributeNames The set of attribute names for this RDN. It must
* not be {@code null} or empty.
* @param attributeValues The set of attribute values for this RDN. It must
* not be {@code null} or empty.
* @param schema The schema to use to generate the normalized
* string representation of this RDN. It may be
* {@code null} if no schema is available.
*/
public RDN(final String[] attributeNames, final String[] attributeValues,
final Schema schema)
{
ensureNotNull(attributeNames, attributeValues);
ensureTrue(attributeNames.length == attributeValues.length,
"RDN.attributeNames and attributeValues must be the same size.");
ensureTrue(attributeNames.length > 0,
"RDN.attributeNames must not be empty.");
this.attributeNames = attributeNames;
this.schema = schema;
this.attributeValues = new ASN1OctetString[attributeValues.length];
for (int i=0; i < attributeValues.length; i++)
{
this.attributeValues[i] = new ASN1OctetString(attributeValues[i]);
}
}
/**
* Creates a new (potentially multivalued) RDN. The set of names must have
* the same number of elements as the set of values, and there must be at
* least one element in each array.
*
* @param attributeNames The set of attribute names for this RDN. It must
* not be {@code null} or empty.
* @param attributeValues The set of attribute values for this RDN. It must
* not be {@code null} or empty.
*/
public RDN(final String[] attributeNames, final byte[][] attributeValues)
{
this(attributeNames, attributeValues, null);
}
/**
* Creates a new (potentially multivalued) RDN. The set of names must have
* the same number of elements as the set of values, and there must be at
* least one element in each array.
*
* @param attributeNames The set of attribute names for this RDN. It must
* not be {@code null} or empty.
* @param attributeValues The set of attribute values for this RDN. It must
* not be {@code null} or empty.
* @param schema The schema to use to generate the normalized
* string representation of this RDN. It may be
* {@code null} if no schema is available.
*/
public RDN(final String[] attributeNames, final byte[][] attributeValues,
final Schema schema)
{
ensureNotNull(attributeNames, attributeValues);
ensureTrue(attributeNames.length == attributeValues.length,
"RDN.attributeNames and attributeValues must be the same size.");
ensureTrue(attributeNames.length > 0,
"RDN.attributeNames must not be empty.");
this.attributeNames = attributeNames;
this.schema = schema;
this.attributeValues = new ASN1OctetString[attributeValues.length];
for (int i=0; i < attributeValues.length; i++)
{
this.attributeValues[i] = new ASN1OctetString(attributeValues[i]);
}
}
/**
* Creates a new single-valued RDN with the provided information.
*
* @param attributeName The name to use for this RDN.
* @param attributeValue The value to use for this RDN.
* @param schema The schema to use to generate the normalized string
* representation of this RDN. It may be {@code null}
* if no schema is available.
* @param rdnString The string representation for this RDN.
*/
RDN(final String attributeName, final ASN1OctetString attributeValue,
final Schema schema, final String rdnString)
{
this.rdnString = rdnString;
this.schema = schema;
attributeNames = new String[] { attributeName };
attributeValues = new ASN1OctetString[] { attributeValue };
}
/**
* Creates a new potentially multivalued RDN with the provided information.
*
* @param attributeNames The set of names to use for this RDN.
* @param attributeValues The set of values to use for this RDN.
* @param rdnString The string representation for this RDN.
* @param schema The schema to use to generate the normalized
* string representation of this RDN. It may be
* {@code null} if no schema is available.
*/
RDN(final String[] attributeNames, final ASN1OctetString[] attributeValues,
final Schema schema, final String rdnString)
{
this.rdnString = rdnString;
this.schema = schema;
this.attributeNames = attributeNames;
this.attributeValues = attributeValues;
}
/**
* Creates a new RDN from the provided string representation.
*
* @param rdnString The string representation to use for this RDN. It must
* not be empty or {@code null}.
*
* @throws LDAPException If the provided string cannot be parsed as a valid
* RDN.
*/
public RDN(final String rdnString)
throws LDAPException
{
this(rdnString, (Schema) null);
}
/**
* Creates a new RDN from the provided string representation.
*
* @param rdnString The string representation to use for this RDN. It must
* not be empty or {@code null}.
* @param schema The schema to use to generate the normalized string
* representation of this RDN. It may be {@code null} if
* no schema is available.
*
* @throws LDAPException If the provided string cannot be parsed as a valid
* RDN.
*/
public RDN(final String rdnString, final Schema schema)
throws LDAPException
{
ensureNotNull(rdnString);
this.rdnString = rdnString;
this.schema = schema;
int pos = 0;
final int length = rdnString.length();
// First, skip over any leading spaces.
while ((pos < length) && (rdnString.charAt(pos) == ' '))
{
pos++;
}
// Read until we find a space or an equal sign. Technically, we should
// ensure that all characters before that point are ASCII letters, numeric
// digits, or dashes, or that it is a valid numeric OID, but since some
// directories allow technically invalid characters in attribute names,
// we'll just blindly take whatever is provided.
int attrStartPos = pos;
while (pos < length)
{
final char c = rdnString.charAt(pos);
if ((c == ' ') || (c == '='))
{
break;
}
pos++;
}
// Extract the attribute name, then skip over any spaces between the
// attribute name and the equal sign.
String attrName = rdnString.substring(attrStartPos, pos);
if (attrName.length() == 0)
{
throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
ERR_RDN_NO_ATTR_NAME.get());
}
while ((pos < length) && (rdnString.charAt(pos) == ' '))
{
pos++;
}
if ((pos >= length) || (rdnString.charAt(pos) != '='))
{
// We didn't find an equal sign.
throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
ERR_RDN_NO_EQUAL_SIGN.get(attrName));
}
// The next character is the equal sign. Skip it, and then skip over any
// spaces between it and the attribute value.
pos++;
while ((pos < length) && (rdnString.charAt(pos) == ' '))
{
pos++;
}
// Look at the next character. If it is an octothorpe (#), then the value
// must be hex-encoded. Otherwise, it's a regular string (although possibly
// containing escaped or quoted characters).
ASN1OctetString value;
if (pos >= length)
{
value = new ASN1OctetString();
}
else if (rdnString.charAt(pos) == '#')
{
// It is a hex-encoded value, so we'll read until we find the end of the
// string or the first non-hex character, which must be either a space or
// a plus sign.
final byte[] valueArray = readHexString(rdnString, ++pos);
value = new ASN1OctetString(valueArray);
pos += (valueArray.length * 2);
}
else
{
// It is a string value, which potentially includes escaped characters.
final StringBuilder buffer = new StringBuilder();
pos = readValueString(rdnString, pos, buffer);
value = new ASN1OctetString(buffer.toString());
}
// Skip over any spaces until we find a plus sign or the end of the value.
while ((pos < length) && (rdnString.charAt(pos) == ' '))
{
pos++;
}
if (pos >= length)
{
// It's a single-valued RDN, so we have everything that we need.
attributeNames = new String[] { attrName };
attributeValues = new ASN1OctetString[] { value };
return;
}
// It's a multivalued RDN, so create temporary lists to hold the names and
// values.
final ArrayList nameList = new ArrayList(5);
final ArrayList valueList =
new ArrayList(5);
nameList.add(attrName);
valueList.add(value);
if (rdnString.charAt(pos) == '+')
{
pos++;
}
else
{
throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
ERR_RDN_VALUE_NOT_FOLLOWED_BY_PLUS.get());
}
if (pos >= length)
{
throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
ERR_RDN_PLUS_NOT_FOLLOWED_BY_AVP.get());
}
int numValues = 1;
while (pos < length)
{
// Skip over any spaces between the plus sign and the attribute name.
while ((pos < length) && (rdnString.charAt(pos) == ' '))
{
pos++;
}
attrStartPos = pos;
while (pos < length)
{
final char c = rdnString.charAt(pos);
if ((c == ' ') || (c == '='))
{
break;
}
pos++;
}
// Skip over any spaces between the attribute name and the equal sign.
attrName = rdnString.substring(attrStartPos, pos);
if (attrName.length() == 0)
{
throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
ERR_RDN_NO_ATTR_NAME.get());
}
while ((pos < length) && (rdnString.charAt(pos) == ' '))
{
pos++;
}
if ((pos >= length) || (rdnString.charAt(pos) != '='))
{
// We didn't find an equal sign.
throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
ERR_RDN_NO_EQUAL_SIGN.get(attrName));
}
// The next character is the equal sign. Skip it, and then skip over any
// spaces between it and the attribute value.
pos++;
while ((pos < length) && (rdnString.charAt(pos) == ' '))
{
pos++;
}
// Look at the next character. If it is an octothorpe (#), then the value
// must be hex-encoded. Otherwise, it's a regular string (although
// possibly containing escaped or quoted characters).
if (pos >= length)
{
value = new ASN1OctetString();
}
else if (rdnString.charAt(pos) == '#')
{
// It is a hex-encoded value, so we'll read until we find the end of the
// string or the first non-hex character, which must be either a space
// or a plus sign.
final byte[] valueArray = readHexString(rdnString, ++pos);
value = new ASN1OctetString(valueArray);
pos += (valueArray.length * 2);
}
else
{
// It is a string value, which potentially includes escaped characters.
final StringBuilder buffer = new StringBuilder();
pos = readValueString(rdnString, pos, buffer);
value = new ASN1OctetString(buffer.toString());
}
// Skip over any spaces until we find a plus sign or the end of the value.
while ((pos < length) && (rdnString.charAt(pos) == ' '))
{
pos++;
}
nameList.add(attrName);
valueList.add(value);
numValues++;
if (pos >= length)
{
// We're at the end of the value, so break out of the loop.
break;
}
else
{
// Skip over the plus sign and loop again to read another name-value
// pair.
if (rdnString.charAt(pos) == '+')
{
pos++;
}
else
{
throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
ERR_RDN_VALUE_NOT_FOLLOWED_BY_PLUS.get());
}
}
if (pos >= length)
{
throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
ERR_RDN_PLUS_NOT_FOLLOWED_BY_AVP.get());
}
}
attributeNames = new String[numValues];
attributeValues = new ASN1OctetString[numValues];
for (int i=0; i < numValues; i++)
{
attributeNames[i] = nameList.get(i);
attributeValues[i] = valueList.get(i);
}
}
/**
* Parses a hex-encoded RDN value from the provided string. Reading will
* continue until the end of the string is reached or a non-escaped plus sign
* is encountered. After returning, the caller should increment its position
* by two times the length of the value array.
*
* @param rdnString The string to be parsed. It should be the position
* immediately after the octothorpe at the start of the
* hex-encoded value.
* @param startPos The position at which to start reading the value.
*
* @return A byte array containing the parsed value.
*
* @throws LDAPException If an error occurs while reading the value (e.g.,
* if it contains non-hex characters, or has an odd
* number of characters.
*/
static byte[] readHexString(final String rdnString, final int startPos)
throws LDAPException
{
final int length = rdnString.length();
int pos = startPos;
final ByteBuffer buffer = ByteBuffer.allocate(length-pos);
hexLoop:
while (pos < length)
{
byte hexByte;
switch (rdnString.charAt(pos++))
{
case '0':
hexByte = 0x00;
break;
case '1':
hexByte = 0x10;
break;
case '2':
hexByte = 0x20;
break;
case '3':
hexByte = 0x30;
break;
case '4':
hexByte = 0x40;
break;
case '5':
hexByte = 0x50;
break;
case '6':
hexByte = 0x60;
break;
case '7':
hexByte = 0x70;
break;
case '8':
hexByte = (byte) 0x80;
break;
case '9':
hexByte = (byte) 0x90;
break;
case 'a':
case 'A':
hexByte = (byte) 0xA0;
break;
case 'b':
case 'B':
hexByte = (byte) 0xB0;
break;
case 'c':
case 'C':
hexByte = (byte) 0xC0;
break;
case 'd':
case 'D':
hexByte = (byte) 0xD0;
break;
case 'e':
case 'E':
hexByte = (byte) 0xE0;
break;
case 'f':
case 'F':
hexByte = (byte) 0xF0;
break;
case ' ':
case '+':
case ',':
case ';':
// This indicates that we've reached the end of the hex string.
break hexLoop;
default:
throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
ERR_RDN_INVALID_HEX_CHAR.get(
rdnString.charAt(pos-1), (pos-1)));
}
if (pos >= length)
{
throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
ERR_RDN_MISSING_HEX_CHAR.get());
}
switch (rdnString.charAt(pos++))
{
case '0':
// No action is required.
break;
case '1':
hexByte |= 0x01;
break;
case '2':
hexByte |= 0x02;
break;
case '3':
hexByte |= 0x03;
break;
case '4':
hexByte |= 0x04;
break;
case '5':
hexByte |= 0x05;
break;
case '6':
hexByte |= 0x06;
break;
case '7':
hexByte |= 0x07;
break;
case '8':
hexByte |= 0x08;
break;
case '9':
hexByte |= 0x09;
break;
case 'a':
case 'A':
hexByte |= 0x0A;
break;
case 'b':
case 'B':
hexByte |= 0x0B;
break;
case 'c':
case 'C':
hexByte |= 0x0C;
break;
case 'd':
case 'D':
hexByte |= 0x0D;
break;
case 'e':
case 'E':
hexByte |= 0x0E;
break;
case 'f':
case 'F':
hexByte |= 0x0F;
break;
default:
throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
ERR_RDN_INVALID_HEX_CHAR.get(
rdnString.charAt(pos-1), (pos-1)));
}
buffer.put(hexByte);
}
buffer.flip();
final byte[] valueArray = new byte[buffer.limit()];
buffer.get(valueArray);
return valueArray;
}
/**
* Reads a string value from the provided RDN string. Reading will continue
* until the end of the string is reached or until a non-escaped plus sign is
* encountered.
*
* @param rdnString The string from which to read the value.
* @param startPos The position in the RDN string at which to start reading
* the value.
* @param buffer The buffer into which the parsed value should be
* placed.
*
* @return The position at which the caller should continue reading when
* parsing the RDN.
*
* @throws LDAPException If a problem occurs while reading the value.
*/
static int readValueString(final String rdnString, final int startPos,
final StringBuilder buffer)
throws LDAPException
{
final int length = rdnString.length();
int pos = startPos;
boolean inQuotes = false;
valueLoop:
while (pos < length)
{
char c = rdnString.charAt(pos);
switch (c)
{
case '\\':
// It's an escaped value. It can either be followed by a single
// character (e.g., backslash, space, octothorpe, equals, double
// quote, plus sign, comma, semicolon, less than, or greater-than), or
// two hex digits. If it is followed by hex digits, then continue
// reading to see if there are more of them.
if ((pos+1) >= length)
{
throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
ERR_RDN_ENDS_WITH_BACKSLASH.get());
}
else
{
pos++;
c = rdnString.charAt(pos);
if (isHex(c))
{
// We need to subtract one from the resulting position because
// it will be incremented later.
pos = readEscapedHexString(rdnString, pos, buffer) - 1;
}
else
{
buffer.append(c);
}
}
break;
case '"':
if (inQuotes)
{
// This should be the end of the value. If it's not, then fail.
pos++;
while (pos < length)
{
c = rdnString.charAt(pos);
if ((c == '+') || (c == ',') || (c == ';'))
{
break;
}
else if (c != ' ')
{
throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
ERR_RDN_CHAR_OUTSIDE_QUOTES.get(c,
(pos-1)));
}
pos++;
}
inQuotes = false;
break valueLoop;
}
else
{
// This should be the first character of the value.
if (pos == startPos)
{
inQuotes = true;
}
else
{
throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
ERR_RDN_UNEXPECTED_DOUBLE_QUOTE.get(pos));
}
}
break;
case ',':
case ';':
case '+':
// This denotes the end of the value, if it's not in quotes.
if (inQuotes)
{
buffer.append(c);
}
else
{
break valueLoop;
}
break;
default:
// This is a normal character that should be added to the buffer.
buffer.append(c);
break;
}
pos++;
}
// If the value started with a quotation mark, then make sure it was closed.
if (inQuotes)
{
throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
ERR_RDN_UNCLOSED_DOUBLE_QUOTE.get());
}
// If the value ends with any unescaped trailing spaces, then trim them off.
int bufferPos = buffer.length() - 1;
int rdnStrPos = pos - 2;
while ((bufferPos > 0) && (buffer.charAt(bufferPos) == ' '))
{
if (rdnString.charAt(rdnStrPos) == '\\')
{
break;
}
else
{
buffer.deleteCharAt(bufferPos--);
rdnStrPos--;
}
}
return pos;
}
/**
* Reads one or more hex-encoded bytes from the specified portion of the RDN
* string.
*
* @param rdnString The string from which the data is to be read.
* @param startPos The position at which to start reading. This should be
* the first hex character immediately after the initial
* backslash.
* @param buffer The buffer to which the decoded string portion should be
* appended.
*
* @return The position at which the caller may resume parsing.
*
* @throws LDAPException If a problem occurs while reading hex-encoded
* bytes.
*/
private static int readEscapedHexString(final String rdnString,
final int startPos,
final StringBuilder buffer)
throws LDAPException
{
final int length = rdnString.length();
int pos = startPos;
final ByteBuffer byteBuffer = ByteBuffer.allocate(length - pos);
while (pos < length)
{
byte b;
switch (rdnString.charAt(pos++))
{
case '0':
b = 0x00;
break;
case '1':
b = 0x10;
break;
case '2':
b = 0x20;
break;
case '3':
b = 0x30;
break;
case '4':
b = 0x40;
break;
case '5':
b = 0x50;
break;
case '6':
b = 0x60;
break;
case '7':
b = 0x70;
break;
case '8':
b = (byte) 0x80;
break;
case '9':
b = (byte) 0x90;
break;
case 'a':
case 'A':
b = (byte) 0xA0;
break;
case 'b':
case 'B':
b = (byte) 0xB0;
break;
case 'c':
case 'C':
b = (byte) 0xC0;
break;
case 'd':
case 'D':
b = (byte) 0xD0;
break;
case 'e':
case 'E':
b = (byte) 0xE0;
break;
case 'f':
case 'F':
b = (byte) 0xF0;
break;
default:
throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
ERR_RDN_INVALID_HEX_CHAR.get(
rdnString.charAt(pos-1), (pos-1)));
}
if (pos >= length)
{
throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
ERR_RDN_MISSING_HEX_CHAR.get());
}
switch (rdnString.charAt(pos++))
{
case '0':
// No action is required.
break;
case '1':
b |= 0x01;
break;
case '2':
b |= 0x02;
break;
case '3':
b |= 0x03;
break;
case '4':
b |= 0x04;
break;
case '5':
b |= 0x05;
break;
case '6':
b |= 0x06;
break;
case '7':
b |= 0x07;
break;
case '8':
b |= 0x08;
break;
case '9':
b |= 0x09;
break;
case 'a':
case 'A':
b |= 0x0A;
break;
case 'b':
case 'B':
b |= 0x0B;
break;
case 'c':
case 'C':
b |= 0x0C;
break;
case 'd':
case 'D':
b |= 0x0D;
break;
case 'e':
case 'E':
b |= 0x0E;
break;
case 'f':
case 'F':
b |= 0x0F;
break;
default:
throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
ERR_RDN_INVALID_HEX_CHAR.get(
rdnString.charAt(pos-1), (pos-1)));
}
byteBuffer.put(b);
if (((pos+1) < length) && (rdnString.charAt(pos) == '\\') &&
isHex(rdnString.charAt(pos+1)))
{
// It appears that there are more hex-encoded bytes to follow, so keep
// reading.
pos++;
continue;
}
else
{
break;
}
}
byteBuffer.flip();
final byte[] byteArray = new byte[byteBuffer.limit()];
byteBuffer.get(byteArray);
try
{
buffer.append(toUTF8String(byteArray));
}
catch (final Exception e)
{
debugException(e);
// This should never happen.
buffer.append(new String(byteArray));
}
return pos;
}
/**
* Indicates whether the provided string represents a valid RDN.
*
* @param s The string for which to make the determination. It must not be
* {@code null}.
*
* @return {@code true} if the provided string represents a valid RDN, or
* {@code false} if not.
*/
public static boolean isValidRDN(final String s)
{
try
{
new RDN(s);
return true;
}
catch (LDAPException le)
{
return false;
}
}
/**
* Indicates whether this RDN contains multiple components.
*
* @return {@code true} if this RDN contains multiple components, or
* {@code false} if not.
*/
public boolean isMultiValued()
{
return (attributeNames.length != 1);
}
/**
* Retrieves an array of the attributes that comprise this RDN.
*
* @return An array of the attributes that comprise this RDN.
*/
public Attribute[] getAttributes()
{
final Attribute[] attrs = new Attribute[attributeNames.length];
for (int i=0; i < attrs.length; i++)
{
attrs[i] = new Attribute(attributeNames[i], schema,
new ASN1OctetString[] { attributeValues[i] });
}
return attrs;
}
/**
* Retrieves the set of attribute names for this RDN.
*
* @return The set of attribute names for this RDN.
*/
public String[] getAttributeNames()
{
return attributeNames;
}
/**
* Retrieves the set of attribute values for this RDN.
*
* @return The set of attribute values for this RDN.
*/
public String[] getAttributeValues()
{
final String[] stringValues = new String[attributeValues.length];
for (int i=0; i < stringValues.length; i++)
{
stringValues[i] = attributeValues[i].stringValue();
}
return stringValues;
}
/**
* Retrieves the set of attribute values for this RDN.
*
* @return The set of attribute values for this RDN.
*/
public byte[][] getByteArrayAttributeValues()
{
final byte[][] byteValues = new byte[attributeValues.length][];
for (int i=0; i < byteValues.length; i++)
{
byteValues[i] = attributeValues[i].getValue();
}
return byteValues;
}
/**
* Retrieves the schema that will be used for this RDN, if any.
*
* @return The schema that will be used for this RDN, or {@code null} if none
* has been provided.
*/
Schema getSchema()
{
return schema;
}
/**
* Indicates whether this RDN contains the specified attribute.
*
* @param attributeName The name of the attribute for which to make the
* determination.
*
* @return {@code true} if RDN contains the specified attribute, or
* {@code false} if not.
*/
public boolean hasAttribute(final String attributeName)
{
for (final String name : attributeNames)
{
if (name.equalsIgnoreCase(attributeName))
{
return true;
}
}
return false;
}
/**
* Indicates whether this RDN contains the specified attribute value.
*
* @param attributeName The name of the attribute for which to make the
* determination.
* @param attributeValue The attribute value for which to make the
* determination.
*
* @return {@code true} if RDN contains the specified attribute, or
* {@code false} if not.
*/
public boolean hasAttributeValue(final String attributeName,
final String attributeValue)
{
for (int i=0; i < attributeNames.length; i++)
{
if (attributeNames[i].equalsIgnoreCase(attributeName))
{
final Attribute a =
new Attribute(attributeName, schema, attributeValue);
final Attribute b = new Attribute(attributeName, schema,
attributeValues[i].stringValue());
if (a.equals(b))
{
return true;
}
}
}
return false;
}
/**
* Indicates whether this RDN contains the specified attribute value.
*
* @param attributeName The name of the attribute for which to make the
* determination.
* @param attributeValue The attribute value for which to make the
* determination.
*
* @return {@code true} if RDN contains the specified attribute, or
* {@code false} if not.
*/
public boolean hasAttributeValue(final String attributeName,
final byte[] attributeValue)
{
for (int i=0; i < attributeNames.length; i++)
{
if (attributeNames[i].equalsIgnoreCase(attributeName))
{
final Attribute a =
new Attribute(attributeName, schema, attributeValue);
final Attribute b = new Attribute(attributeName, schema,
attributeValues[i].getValue());
if (a.equals(b))
{
return true;
}
}
}
return false;
}
/**
* Retrieves a string representation of this RDN.
*
* @return A string representation of this RDN.
*/
@Override()
public String toString()
{
if (rdnString == null)
{
final StringBuilder buffer = new StringBuilder();
toString(buffer, false);
rdnString = buffer.toString();
}
return rdnString;
}
/**
* Retrieves a string representation of this RDN with minimal encoding for
* special characters. Only those characters specified in RFC 4514 section
* 2.4 will be escaped. No escaping will be used for non-ASCII characters or
* non-printable ASCII characters.
*
* @return A string representation of this RDN with minimal encoding for
* special characters.
*/
public String toMinimallyEncodedString()
{
final StringBuilder buffer = new StringBuilder();
toString(buffer, true);
return buffer.toString();
}
/**
* Appends a string representation of this RDN to the provided buffer.
*
* @param buffer The buffer to which the string representation is to be
* appended.
*/
public void toString(final StringBuilder buffer)
{
toString(buffer, false);
}
/**
* Appends a string representation of this RDN to the provided buffer.
*
* @param buffer The buffer to which the string representation is
* to be appended.
* @param minimizeEncoding Indicates whether to restrict the encoding of
* special characters to the bare minimum required
* by LDAP (as per RFC 4514 section 2.4). If this
* is {@code true}, then only leading and trailing
* spaces, double quotes, plus signs, commas,
* semicolons, greater-than, less-than, and
* backslash characters will be encoded.
*/
public void toString(final StringBuilder buffer,
final boolean minimizeEncoding)
{
if ((rdnString != null) && (! minimizeEncoding))
{
buffer.append(rdnString);
return;
}
for (int i=0; i < attributeNames.length; i++)
{
if (i > 0)
{
buffer.append('+');
}
buffer.append(attributeNames[i]);
buffer.append('=');
// Iterate through the value character-by-character and do any escaping
// that may be necessary.
final String valueString = attributeValues[i].stringValue();
final int length = valueString.length();
for (int j=0; j < length; j++)
{
final char c = valueString.charAt(j);
switch (c)
{
case '\\':
case '#':
case '=':
case '"':
case '+':
case ',':
case ';':
case '<':
case '>':
buffer.append('\\');
buffer.append(c);
break;
case ' ':
// Escape this space only if it's the first character, the last
// character, or if the next character is also a space.
if ((j == 0) || ((j+1) == length) ||
(((j+1) < length) && (valueString.charAt(j+1) == ' ')))
{
buffer.append("\\ ");
}
else
{
buffer.append(' ');
}
break;
case '\u0000':
buffer.append("\\00");
break;
default:
// If it's not a printable ASCII character, then hex-encode it
// unless we're using minimized encoding.
if ((! minimizeEncoding) && ((c < ' ') || (c > '~')))
{
hexEncode(c, buffer);
}
else
{
buffer.append(c);
}
break;
}
}
}
}
/**
* Retrieves a normalized string representation of this RDN.
*
* @return A normalized string representation of this RDN.
*/
public String toNormalizedString()
{
if (normalizedString == null)
{
final StringBuilder buffer = new StringBuilder();
toNormalizedString(buffer);
normalizedString = buffer.toString();
}
return normalizedString;
}
/**
* Appends a normalized string representation of this RDN to the provided
* buffer.
*
* @param buffer The buffer to which the normalized string representation is
* to be appended.
*/
public void toNormalizedString(final StringBuilder buffer)
{
if (attributeNames.length == 1)
{
// It's a single-valued RDN, so there is no need to sort anything.
final String name = normalizeAttrName(attributeNames[0]);
buffer.append(name);
buffer.append('=');
buffer.append(normalizeValue(name, attributeValues[0]));
}
else
{
// It's a multivalued RDN, so we need to sort the components.
final TreeMap valueMap =
new TreeMap();
for (int i=0; i < attributeNames.length; i++)
{
final String name = normalizeAttrName(attributeNames[i]);
valueMap.put(name, attributeValues[i]);
}
int i=0;
for (final Map.Entry entry : valueMap.entrySet())
{
if (i++ > 0)
{
buffer.append('+');
}
buffer.append(entry.getKey());
buffer.append('=');
buffer.append(normalizeValue(entry.getKey(), entry.getValue()));
}
}
}
/**
* Obtains a normalized representation of the provided attribute name.
*
* @param name The name of the attribute for which to create the normalized
* representation.
*
* @return A normalized representation of the provided attribute name.
*/
private String normalizeAttrName(final String name)
{
String n = name;
if (schema != null)
{
final AttributeTypeDefinition at = schema.getAttributeType(name);
if (at != null)
{
n = at.getNameOrOID();
}
}
return toLowerCase(n);
}
/**
* Retrieves a normalized string representation of the RDN with the provided
* string representation.
*
* @param s The string representation of the RDN to normalize. It must not
* be {@code null}.
*
* @return The normalized string representation of the RDN with the provided
* string representation.
*
* @throws LDAPException If the provided string cannot be parsed as an RDN.
*/
public static String normalize(final String s)
throws LDAPException
{
return normalize(s, null);
}
/**
* Retrieves a normalized string representation of the RDN with the provided
* string representation.
*
* @param s The string representation of the RDN to normalize. It must
* not be {@code null}.
* @param schema The schema to use to generate the normalized string
* representation of the RDN. It may be {@code null} if no
* schema is available.
*
* @return The normalized string representation of the RDN with the provided
* string representation.
*
* @throws LDAPException If the provided string cannot be parsed as an RDN.
*/
public static String normalize(final String s, final Schema schema)
throws LDAPException
{
return new RDN(s, schema).toNormalizedString();
}
/**
* Normalizes the provided attribute value for use in an RDN.
*
* @param attributeName The name of the attribute with which the value is
* associated.
* @param value The value to be normalized.
*
* @return A string builder containing a normalized representation of the
* value in a suitable form for inclusion in an RDN.
*/
private StringBuilder normalizeValue(final String attributeName,
final ASN1OctetString value)
{
final MatchingRule matchingRule =
MatchingRule.selectEqualityMatchingRule(attributeName, schema);
ASN1OctetString rawNormValue;
try
{
rawNormValue = matchingRule.normalize(value);
}
catch (final Exception e)
{
debugException(e);
rawNormValue =
new ASN1OctetString(toLowerCase(value.stringValue()));
}
final String valueString = rawNormValue.stringValue();
final int length = valueString.length();
final StringBuilder buffer = new StringBuilder(length);
for (int i=0; i < length; i++)
{
final char c = valueString.charAt(i);
switch (c)
{
case '\\':
case '#':
case '=':
case '"':
case '+':
case ',':
case ';':
case '<':
case '>':
buffer.append('\\');
buffer.append(c);
break;
case ' ':
// Escape this space only if it's the first character, the last
// character, or if the next character is also a space.
if ((i == 0) || ((i+1) == length) ||
(((i+1) < length) && (valueString.charAt(i+1) == ' ')))
{
buffer.append("\\ ");
}
else
{
buffer.append(' ');
}
break;
default:
// If it's not a printable ASCII character, then hex-encode it.
if ((c < ' ') || (c > '~'))
{
hexEncode(c, buffer);
}
else
{
buffer.append(c);
}
break;
}
}
return buffer;
}
/**
* Retrieves a hash code for this RDN.
*
* @return The hash code for this RDN.
*/
@Override()
public int hashCode()
{
return toNormalizedString().hashCode();
}
/**
* Indicates whether this RDN is equal to the provided object. The given
* object will only be considered equal to this RDN if it is also an RDN with
* the same set of names and values.
*
* @param o The object for which to make the determination.
*
* @return {@code true} if the provided object can be considered equal to
* this RDN, or {@code false} if not.
*/
@Override()
public boolean equals(final Object o)
{
if (o == null)
{
return false;
}
if (o == this)
{
return true;
}
if (! (o instanceof RDN))
{
return false;
}
final RDN rdn = (RDN) o;
return (toNormalizedString().equals(rdn.toNormalizedString()));
}
/**
* Indicates whether the RDN with the provided string representation is equal
* to this RDN.
*
* @param s The string representation of the DN to compare with this RDN.
*
* @return {@code true} if the DN with the provided string representation is
* equal to this RDN, or {@code false} if not.
*
* @throws LDAPException If the provided string cannot be parsed as an RDN.
*/
public boolean equals(final String s)
throws LDAPException
{
if (s == null)
{
return false;
}
return equals(new RDN(s, schema));
}
/**
* Indicates whether the two provided strings represent the same RDN.
*
* @param s1 The string representation of the first RDN for which to make
* the determination. It must not be {@code null}.
* @param s2 The string representation of the second RDN for which to make
* the determination. It must not be {@code null}.
*
* @return {@code true} if the provided strings represent the same RDN, or
* {@code false} if not.
*
* @throws LDAPException If either of the provided strings cannot be parsed
* as an RDN.
*/
public static boolean equals(final String s1, final String s2)
throws LDAPException
{
return new RDN(s1).equals(new RDN(s2));
}
/**
* Compares the provided RDN to this RDN to determine their relative order in
* a sorted list.
*
* @param rdn The RDN to compare against this RDN. It must not be
* {@code null}.
*
* @return A negative integer if this RDN should come before the provided RDN
* in a sorted list, a positive integer if this RDN should come after
* the provided RDN in a sorted list, or zero if the provided RDN
* can be considered equal to this RDN.
*/
public int compareTo(final RDN rdn)
{
return compare(this, rdn);
}
/**
* Compares the provided RDN values to determine their relative order in a
* sorted list.
*
* @param rdn1 The first RDN to be compared. It must not be {@code null}.
* @param rdn2 The second RDN to be compared. It must not be {@code null}.
*
* @return A negative integer if the first RDN should come before the second
* RDN in a sorted list, a positive integer if the first RDN should
* come after the second RDN in a sorted list, or zero if the two RDN
* values can be considered equal.
*/
public int compare(final RDN rdn1, final RDN rdn2)
{
ensureNotNull(rdn1, rdn2);
return(rdn1.toNormalizedString().compareTo(rdn2.toNormalizedString()));
}
/**
* Compares the RDN values with the provided string representations to
* determine their relative order in a sorted list.
*
* @param s1 The string representation of the first RDN to be compared. It
* must not be {@code null}.
* @param s2 The string representation of the second RDN to be compared. It
* must not be {@code null}.
*
* @return A negative integer if the first RDN should come before the second
* RDN in a sorted list, a positive integer if the first RDN should
* come after the second RDN in a sorted list, or zero if the two RDN
* values can be considered equal.
*
* @throws LDAPException If either of the provided strings cannot be parsed
* as an RDN.
*/
public static int compare(final String s1, final String s2)
throws LDAPException
{
return compare(s1, s2, null);
}
/**
* Compares the RDN values with the provided string representations to
* determine their relative order in a sorted list.
*
* @param s1 The string representation of the first RDN to be compared.
* It must not be {@code null}.
* @param s2 The string representation of the second RDN to be compared.
* It must not be {@code null}.
* @param schema The schema to use to generate the normalized string
* representations of the RDNs. It may be {@code null} if no
* schema is available.
*
* @return A negative integer if the first RDN should come before the second
* RDN in a sorted list, a positive integer if the first RDN should
* come after the second RDN in a sorted list, or zero if the two RDN
* values can be considered equal.
*
* @throws LDAPException If either of the provided strings cannot be parsed
* as an RDN.
*/
public static int compare(final String s1, final String s2,
final Schema schema)
throws LDAPException
{
return new RDN(s1, schema).compareTo(new RDN(s2, schema));
}
}