package com.hwlcn.ldap.ldap.sdk;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import com.hwlcn.ldap.asn1.ASN1Buffer;
import com.hwlcn.ldap.asn1.ASN1BufferSequence;
import com.hwlcn.ldap.asn1.ASN1BufferSet;
import com.hwlcn.ldap.asn1.ASN1Element;
import com.hwlcn.ldap.asn1.ASN1Exception;
import com.hwlcn.ldap.asn1.ASN1OctetString;
import com.hwlcn.ldap.asn1.ASN1Sequence;
import com.hwlcn.ldap.asn1.ASN1Set;
import com.hwlcn.ldap.asn1.ASN1StreamReader;
import com.hwlcn.ldap.asn1.ASN1StreamReaderSet;
import com.hwlcn.ldap.ldap.matchingrules.CaseIgnoreStringMatchingRule;
import com.hwlcn.ldap.ldap.matchingrules.MatchingRule;
import com.hwlcn.ldap.ldap.sdk.schema.Schema;
import com.hwlcn.ldap.util.Base64;
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.Debug.*;
import static com.hwlcn.ldap.util.StaticUtils.*;
import static com.hwlcn.ldap.util.Validator.*;
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class Attribute
implements Serializable
{
private static final ASN1OctetString[] NO_VALUES = new ASN1OctetString[0];
private static final byte[][] NO_BYTE_VALUES = new byte[0][];
private static final long serialVersionUID = 5867076498293567612L;
private final ASN1OctetString[] values;
private int hashCode = -1;
private final MatchingRule matchingRule;
private final String name;
public Attribute(final String name)
{
ensureNotNull(name);
this.name = name;
values = NO_VALUES;
matchingRule = CaseIgnoreStringMatchingRule.getInstance();
}
public Attribute(final String name, final String value)
{
ensureNotNull(name, value);
this.name = name;
values = new ASN1OctetString[] { new ASN1OctetString(value) };
matchingRule = CaseIgnoreStringMatchingRule.getInstance();
}
public Attribute(final String name, final byte[] value)
{
ensureNotNull(name, value);
this.name = name;
values = new ASN1OctetString[] { new ASN1OctetString(value) };
matchingRule = CaseIgnoreStringMatchingRule.getInstance();
}
public Attribute(final String name, final String... values)
{
ensureNotNull(name, values);
this.name = name;
this.values = new ASN1OctetString[values.length];
for (int i=0; i < values.length; i++)
{
this.values[i] = new ASN1OctetString(values[i]);
}
matchingRule = CaseIgnoreStringMatchingRule.getInstance();
}
public Attribute(final String name, final byte[]... values)
{
ensureNotNull(name, values);
this.name = name;
this.values = new ASN1OctetString[values.length];
for (int i=0; i < values.length; i++)
{
this.values[i] = new ASN1OctetString(values[i]);
}
matchingRule = CaseIgnoreStringMatchingRule.getInstance();
}
public Attribute(final String name, final ASN1OctetString... values)
{
ensureNotNull(name, values);
this.name = name;
this.values = values;
matchingRule = CaseIgnoreStringMatchingRule.getInstance();
}
public Attribute(final String name, final Collection<String> values)
{
ensureNotNull(name, values);
this.name = name;
this.values = new ASN1OctetString[values.size()];
int i=0;
for (final String s : values)
{
this.values[i++] = new ASN1OctetString(s);
}
matchingRule = CaseIgnoreStringMatchingRule.getInstance();
}
public Attribute(final String name, final MatchingRule matchingRule)
{
ensureNotNull(name, matchingRule);
this.name = name;
this.matchingRule = matchingRule;
values = NO_VALUES;
}
public Attribute(final String name, final MatchingRule matchingRule,
final String value)
{
ensureNotNull(name, matchingRule, value);
this.name = name;
this.matchingRule = matchingRule;
values = new ASN1OctetString[] { new ASN1OctetString(value) };
}
public Attribute(final String name, final MatchingRule matchingRule,
final byte[] value)
{
ensureNotNull(name, matchingRule, value);
this.name = name;
this.matchingRule = matchingRule;
values = new ASN1OctetString[] { new ASN1OctetString(value) };
}
public Attribute(final String name, final MatchingRule matchingRule,
final String... values)
{
ensureNotNull(name, matchingRule, values);
this.name = name;
this.matchingRule = matchingRule;
this.values = new ASN1OctetString[values.length];
for (int i=0; i < values.length; i++)
{
this.values[i] = new ASN1OctetString(values[i]);
}
}
public Attribute(final String name, final MatchingRule matchingRule,
final byte[]... values)
{
ensureNotNull(name, matchingRule, values);
this.name = name;
this.matchingRule = matchingRule;
this.values = new ASN1OctetString[values.length];
for (int i=0; i < values.length; i++)
{
this.values[i] = new ASN1OctetString(values[i]);
}
}
public Attribute(final String name, final MatchingRule matchingRule,
final Collection<String> values)
{
ensureNotNull(name, matchingRule, values);
this.name = name;
this.matchingRule = matchingRule;
this.values = new ASN1OctetString[values.size()];
int i=0;
for (final String s : values)
{
this.values[i++] = new ASN1OctetString(s);
}
}
public Attribute(final String name, final MatchingRule matchingRule,
final ASN1OctetString[] values)
{
this.name = name;
this.matchingRule = matchingRule;
this.values = values;
}
public Attribute(final String name, final Schema schema,
final String... values)
{
this(name, MatchingRule.selectEqualityMatchingRule(name, schema), values);
}
public Attribute(final String name, final Schema schema,
final byte[]... values)
{
this(name, MatchingRule.selectEqualityMatchingRule(name, schema), values);
}
public Attribute(final String name, final Schema schema,
final Collection<String> values)
{
this(name, MatchingRule.selectEqualityMatchingRule(name, schema), values);
}
public Attribute(final String name, final Schema schema,
final ASN1OctetString[] values)
{
this(name, MatchingRule.selectEqualityMatchingRule(name, schema), values);
}
public static Attribute mergeAttributes(final Attribute attr1,
final Attribute attr2)
{
ensureNotNull(attr1, attr2);
final String name = attr1.name;
ensureTrue(name.equalsIgnoreCase(attr2.name));
final MatchingRule matchingRule = attr1.matchingRule;
ASN1OctetString[] mergedValues =
new ASN1OctetString[attr1.values.length + attr2.values.length];
System.arraycopy(attr1.values, 0, mergedValues, 0, attr1.values.length);
int pos = attr1.values.length;
for (final ASN1OctetString s2 : attr2.values)
{
boolean found = false;
for (final ASN1OctetString s1 : attr1.values)
{
try
{
if (matchingRule.valuesMatch(s1, s2))
{
found = true;
break;
}
}
catch (Exception e)
{
debugException(e);
}
}
if (! found)
{
mergedValues[pos++] = s2;
}
}
if (pos != mergedValues.length)
{
final ASN1OctetString[] newMergedValues = new ASN1OctetString[pos];
System.arraycopy(mergedValues, 0, newMergedValues, 0, pos);
mergedValues = newMergedValues;
}
return new Attribute(name, matchingRule, mergedValues);
}
public static Attribute removeValues(final Attribute attr1,
final Attribute attr2)
{
return removeValues(attr1, attr2, attr1.matchingRule);
}
public static Attribute removeValues(final Attribute attr1,
final Attribute attr2,
final MatchingRule matchingRule)
{
ensureNotNull(attr1, attr2);
final String name = attr1.name;
ensureTrue(name.equalsIgnoreCase(attr2.name));
final MatchingRule mr;
if (matchingRule == null)
{
mr = attr1.matchingRule;
}
else
{
mr = matchingRule;
}
final ArrayList<ASN1OctetString> newValues =
new ArrayList<ASN1OctetString>(Arrays.asList(attr1.values));
final Iterator<ASN1OctetString> iterator = newValues.iterator();
while (iterator.hasNext())
{
if (attr2.hasValue(iterator.next(), mr))
{
iterator.remove();
}
}
final ASN1OctetString[] newValueArray =
new ASN1OctetString[newValues.size()];
newValues.toArray(newValueArray);
return new Attribute(name, mr, newValueArray);
}
public String getName()
{
return name;
}
public String getBaseName()
{
return getBaseName(name);
}
public static String getBaseName(final String name)
{
final int semicolonPos = name.indexOf(';');
if (semicolonPos > 0)
{
return name.substring(0, semicolonPos);
}
else
{
return name;
}
}
public boolean nameIsValid()
{
return nameIsValid(name, true);
}
public static boolean nameIsValid(final String s)
{
return nameIsValid(s, true);
}
public static boolean nameIsValid(final String s, final boolean allowOptions)
{
final int length;
if ((s == null) || ((length = s.length()) == 0))
{
return false;
}
final char firstChar = s.charAt(0);
if (! (((firstChar >= 'a') && (firstChar <= 'z')) ||
((firstChar >= 'A') && (firstChar <= 'Z'))))
{
return false;
}
boolean lastWasSemiColon = false;
for (int i=1; i < length; i++)
{
final char c = s.charAt(i);
if (((c >= 'a') && (c <= 'z')) ||
((c >= 'A') && (c <= 'Z')))
{
lastWasSemiColon = false;
}
else if (((c >= '0') && (c <= '9')) ||
(c == '-'))
{
if (lastWasSemiColon)
{
return false;
}
lastWasSemiColon = false;
}
else if (c == ';')
{
if (lastWasSemiColon || (! allowOptions))
{
return false;
}
lastWasSemiColon = true;
}
else
{
return false;
}
}
return (! lastWasSemiColon);
}
public boolean hasOptions()
{
return hasOptions(name);
}
public static boolean hasOptions(final String name)
{
return (name.indexOf(';') > 0);
}
public boolean hasOption(final String option)
{
return hasOption(name, option);
}
public static boolean hasOption(final String name, final String option)
{
final Set<String> options = getOptions(name);
for (final String s : options)
{
if (s.equalsIgnoreCase(option))
{
return true;
}
}
return false;
}
public Set<String> getOptions()
{
return getOptions(name);
}
public static Set<String> getOptions(final String name)
{
int semicolonPos = name.indexOf(';');
if (semicolonPos > 0)
{
final LinkedHashSet<String> options = new LinkedHashSet<String>();
while (true)
{
final int nextSemicolonPos = name.indexOf(';', semicolonPos+1);
if (nextSemicolonPos > 0)
{
options.add(name.substring(semicolonPos+1, nextSemicolonPos));
semicolonPos = nextSemicolonPos;
}
else
{
options.add(name.substring(semicolonPos+1));
break;
}
}
return Collections.unmodifiableSet(options);
}
else
{
return Collections.emptySet();
}
}
public MatchingRule getMatchingRule()
{
return matchingRule;
}
public String getValue()
{
if (values.length == 0)
{
return null;
}
return values[0].stringValue();
}
public byte[] getValueByteArray()
{
if (values.length == 0)
{
return null;
}
return values[0].getValue();
}
public Boolean getValueAsBoolean()
{
if (values.length == 0)
{
return null;
}
final String lowerValue = toLowerCase(values[0].stringValue());
if (lowerValue.equals("true") || lowerValue.equals("t") ||
lowerValue.equals("yes") || lowerValue.equals("y") ||
lowerValue.equals("on") || lowerValue.equals("1"))
{
return Boolean.TRUE;
}
else if (lowerValue.equals("false") || lowerValue.equals("f") ||
lowerValue.equals("no") || lowerValue.equals("n") ||
lowerValue.equals("off") || lowerValue.equals("0"))
{
return Boolean.FALSE;
}
else
{
return null;
}
}
public Date getValueAsDate()
{
if (values.length == 0)
{
return null;
}
try
{
return decodeGeneralizedTime(values[0].stringValue());
}
catch (Exception e)
{
debugException(e);
return null;
}
}
public DN getValueAsDN()
{
if (values.length == 0)
{
return null;
}
try
{
return new DN(values[0].stringValue());
}
catch (Exception e)
{
debugException(e);
return null;
}
}
public Integer getValueAsInteger()
{
if (values.length == 0)
{
return null;
}
try
{
return Integer.valueOf(values[0].stringValue());
}
catch (NumberFormatException nfe)
{
debugException(nfe);
return null;
}
}
public Long getValueAsLong()
{
if (values.length == 0)
{
return null;
}
try
{
return Long.valueOf(values[0].stringValue());
}
catch (NumberFormatException nfe)
{
debugException(nfe);
return null;
}
}
public String[] getValues()
{
if (values.length == 0)
{
return NO_STRINGS;
}
final String[] stringValues = new String[values.length];
for (int i=0; i < values.length; i++)
{
stringValues[i] = values[i].stringValue();
}
return stringValues;
}
public byte[][] getValueByteArrays()
{
if (values.length == 0)
{
return NO_BYTE_VALUES;
}
final byte[][] byteValues = new byte[values.length][];
for (int i=0; i < values.length; i++)
{
byteValues[i] = values[i].getValue();
}
return byteValues;
}
public ASN1OctetString[] getRawValues()
{
return values;
}
public boolean hasValue()
{
return (values.length > 0);
}
public boolean hasValue(final String value)
{
ensureNotNull(value);
return hasValue(new ASN1OctetString(value), matchingRule);
}
public boolean hasValue(final String value, final MatchingRule matchingRule)
{
ensureNotNull(value);
return hasValue(new ASN1OctetString(value), matchingRule);
}
public boolean hasValue(final byte[] value)
{
ensureNotNull(value);
return hasValue(new ASN1OctetString(value), matchingRule);
}
public boolean hasValue(final byte[] value, final MatchingRule matchingRule)
{
ensureNotNull(value);
return hasValue(new ASN1OctetString(value), matchingRule);
}
boolean hasValue(final ASN1OctetString value)
{
return hasValue(value, matchingRule);
}
boolean hasValue(final ASN1OctetString value, final MatchingRule matchingRule)
{
for (final ASN1OctetString existingValue : values)
{
try
{
if (matchingRule.valuesMatch(existingValue, value))
{
return true;
}
}
catch (final LDAPException le)
{
debugException(le);
if (existingValue.equals(value))
{
return true;
}
}
}
return false;
}
public int size()
{
return values.length;
}
public void writeTo(final ASN1Buffer buffer)
{
final ASN1BufferSequence attrSequence = buffer.beginSequence();
buffer.addOctetString(name);
final ASN1BufferSet valueSet = buffer.beginSet();
for (final ASN1OctetString value : values)
{
buffer.addElement(value);
}
valueSet.end();
attrSequence.end();
}
public ASN1Sequence encode()
{
final ASN1Element[] elements =
{
new ASN1OctetString(name),
new ASN1Set(values)
};
return new ASN1Sequence(elements);
}
public static Attribute readFrom(final ASN1StreamReader reader)
throws LDAPException
{
return readFrom(reader, null);
}
public static Attribute readFrom(final ASN1StreamReader reader,
final Schema schema)
throws LDAPException
{
try
{
ensureNotNull(reader.beginSequence());
final String attrName = reader.readString();
ensureNotNull(attrName);
final MatchingRule matchingRule =
MatchingRule.selectEqualityMatchingRule(attrName, schema);
final ArrayList<ASN1OctetString> valueList =
new ArrayList<ASN1OctetString>();
final ASN1StreamReaderSet valueSet = reader.beginSet();
while (valueSet.hasMoreElements())
{
valueList.add(new ASN1OctetString(reader.readBytes()));
}
final ASN1OctetString[] values = new ASN1OctetString[valueList.size()];
valueList.toArray(values);
return new Attribute(attrName, matchingRule, values);
}
catch (Exception e)
{
debugException(e);
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_ATTR_CANNOT_DECODE.get(getExceptionMessage(e)), e);
}
}
public static Attribute decode(final ASN1Sequence encodedAttribute)
throws LDAPException
{
ensureNotNull(encodedAttribute);
final ASN1Element[] elements = encodedAttribute.elements();
if (elements.length != 2)
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_ATTR_DECODE_INVALID_COUNT.get(elements.length));
}
final String name =
ASN1OctetString.decodeAsOctetString(elements[0]).stringValue();
final ASN1Set valueSet;
try
{
valueSet = ASN1Set.decodeAsSet(elements[1]);
}
catch (ASN1Exception ae)
{
debugException(ae);
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_ATTR_DECODE_VALUE_SET.get(getExceptionMessage(ae)), ae);
}
final ASN1OctetString[] values =
new ASN1OctetString[valueSet.elements().length];
for (int i=0; i < values.length; i++)
{
values[i] = ASN1OctetString.decodeAsOctetString(valueSet.elements()[i]);
}
return new Attribute(name, CaseIgnoreStringMatchingRule.getInstance(),
values);
}
public boolean needsBase64Encoding()
{
for (final ASN1OctetString v : values)
{
if (needsBase64Encoding(v.getValue()))
{
return true;
}
}
return false;
}
public static boolean needsBase64Encoding(final String v)
{
return needsBase64Encoding(getBytes(v));
}
public static boolean needsBase64Encoding(final byte[] v)
{
if (v.length == 0)
{
return false;
}
switch (v[0] & 0xFF)
{
case 0x20:
case 0x3A:
case 0x3C:
return true;
}
if ((v[v.length-1] & 0xFF) == 0x20)
{
return true;
}
for (final byte b : v)
{
switch (b & 0xFF)
{
case 0x00:
case 0x0A:
case 0x0D:
return true;
default:
if ((b & 0x80) != 0x00)
{
return true;
}
break;
}
}
return false;
}
@Override()
public int hashCode()
{
if (hashCode == -1)
{
int c = toLowerCase(name).hashCode();
for (final ASN1OctetString value : values)
{
try
{
c += matchingRule.normalize(value).hashCode();
}
catch (LDAPException le)
{
debugException(le);
c += value.hashCode();
}
}
hashCode = c;
}
return hashCode;
}
@Override()
public boolean equals(final Object o)
{
if (o == null)
{
return false;
}
if (o == this)
{
return true;
}
if (! (o instanceof Attribute))
{
return false;
}
final Attribute a = (Attribute) o;
if (! name.equalsIgnoreCase(a.name))
{
return false;
}
if (values.length != a.values.length)
{
return false;
}
if (values.length > 10)
{
final HashSet<ASN1OctetString> unNormalizedValues =
new HashSet<ASN1OctetString>(values.length);
Collections.addAll(unNormalizedValues, values);
HashSet<ASN1OctetString> normalizedMissingValues = null;
for (final ASN1OctetString value : a.values)
{
if (! unNormalizedValues.remove(value))
{
if (normalizedMissingValues == null)
{
normalizedMissingValues =
new HashSet<ASN1OctetString>(values.length);
}
try
{
normalizedMissingValues.add(matchingRule.normalize(value));
}
catch (final Exception e)
{
debugException(e);
return false;
}
}
}
if (normalizedMissingValues != null)
{
for (final ASN1OctetString value : unNormalizedValues)
{
try
{
if (! normalizedMissingValues.contains(
matchingRule.normalize(value)))
{
return false;
}
}
catch (final Exception e)
{
debugException(e);
return false;
}
}
}
}
else
{
for (final ASN1OctetString value : values)
{
if (! a.hasValue(value))
{
return false;
}
}
}
return true;
}
@Override()
public String toString()
{
final StringBuilder buffer = new StringBuilder();
toString(buffer);
return buffer.toString();
}
public void toString(final StringBuilder buffer)
{
buffer.append("Attribute(name=");
buffer.append(name);
if (values.length == 0)
{
buffer.append(", values={");
}
else if (needsBase64Encoding())
{
buffer.append(", base64Values={'");
for (int i=0; i < values.length; i++)
{
if (i > 0)
{
buffer.append("', '");
}
buffer.append(Base64.encode(values[i].getValue()));
}
buffer.append('\'');
}
else
{
buffer.append(", values={'");
for (int i=0; i < values.length; i++)
{
if (i > 0)
{
buffer.append("', '");
}
buffer.append(values[i].stringValue());
}
buffer.append('\'');
}
buffer.append("})");
}
}