package com.hwlcn.ldap.ldap.sdk.schema;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Set;
import com.hwlcn.ldap.ldap.sdk.LDAPException;
import com.hwlcn.ldap.ldap.sdk.ResultCode;
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.schema.SchemaMessages.*;
import static com.hwlcn.ldap.util.StaticUtils.*;
import static com.hwlcn.ldap.util.Validator.*;
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class ObjectClassDefinition
extends SchemaElement
{
private static final long serialVersionUID = -3024333376249332728L;
private final boolean isObsolete;
private final Map<String,String[]> extensions;
private final ObjectClassType objectClassType;
private final String description;
private final String objectClassString;
private final String oid;
private final String[] names;
private final String[] optionalAttributes;
private final String[] requiredAttributes;
private final String[] superiorClasses;
public ObjectClassDefinition(final String s)
throws LDAPException
{
ensureNotNull(s);
objectClassString = s.trim();
final int length = objectClassString.length();
if (length == 0)
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_OC_DECODE_EMPTY.get());
}
else if (objectClassString.charAt(0) != '(')
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_OC_DECODE_NO_OPENING_PAREN.get(
objectClassString));
}
int pos = skipSpaces(objectClassString, 1, length);
StringBuilder buffer = new StringBuilder();
pos = readOID(objectClassString, pos, length, buffer);
oid = buffer.toString();
final ArrayList<String> nameList = new ArrayList<String>(1);
final ArrayList<String> supList = new ArrayList<String>(1);
final ArrayList<String> reqAttrs = new ArrayList<String>();
final ArrayList<String> optAttrs = new ArrayList<String>();
final Map<String,String[]> exts = new LinkedHashMap<String,String[]>();
Boolean obsolete = null;
ObjectClassType ocType = null;
String descr = null;
while (true)
{
pos = skipSpaces(objectClassString, pos, length);
final int tokenStartPos = pos;
while ((pos < length) && (objectClassString.charAt(pos) != ' '))
{
pos++;
}
String token = objectClassString.substring(tokenStartPos, pos);
if ((token.length() > 1) && (token.endsWith(")")))
{
token = token.substring(0, token.length() - 1);
pos--;
}
final String lowerToken = toLowerCase(token);
if (lowerToken.equals(")"))
{
if (pos < length)
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_OC_DECODE_CLOSE_NOT_AT_END.get(
objectClassString));
}
break;
}
else if (lowerToken.equals("name"))
{
if (nameList.isEmpty())
{
pos = skipSpaces(objectClassString, pos, length);
pos = readQDStrings(objectClassString, pos, length, nameList);
}
else
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_OC_DECODE_MULTIPLE_ELEMENTS.get(
objectClassString, "NAME"));
}
}
else if (lowerToken.equals("desc"))
{
if (descr == null)
{
pos = skipSpaces(objectClassString, pos, length);
buffer = new StringBuilder();
pos = readQDString(objectClassString, pos, length, buffer);
descr = buffer.toString();
}
else
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_OC_DECODE_MULTIPLE_ELEMENTS.get(
objectClassString, "DESC"));
}
}
else if (lowerToken.equals("obsolete"))
{
if (obsolete == null)
{
obsolete = true;
}
else
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_OC_DECODE_MULTIPLE_ELEMENTS.get(
objectClassString, "OBSOLETE"));
}
}
else if (lowerToken.equals("sup"))
{
if (supList.isEmpty())
{
pos = skipSpaces(objectClassString, pos, length);
pos = readOIDs(objectClassString, pos, length, supList);
}
else
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_OC_DECODE_MULTIPLE_ELEMENTS.get(
objectClassString, "SUP"));
}
}
else if (lowerToken.equals("abstract"))
{
if (ocType == null)
{
ocType = ObjectClassType.ABSTRACT;
}
else
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_OC_DECODE_MULTIPLE_OC_TYPES.get(
objectClassString));
}
}
else if (lowerToken.equals("structural"))
{
if (ocType == null)
{
ocType = ObjectClassType.STRUCTURAL;
}
else
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_OC_DECODE_MULTIPLE_OC_TYPES.get(
objectClassString));
}
}
else if (lowerToken.equals("auxiliary"))
{
if (ocType == null)
{
ocType = ObjectClassType.AUXILIARY;
}
else
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_OC_DECODE_MULTIPLE_OC_TYPES.get(
objectClassString));
}
}
else if (lowerToken.equals("must"))
{
if (reqAttrs.isEmpty())
{
pos = skipSpaces(objectClassString, pos, length);
pos = readOIDs(objectClassString, pos, length, reqAttrs);
}
else
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_OC_DECODE_MULTIPLE_ELEMENTS.get(
objectClassString, "MUST"));
}
}
else if (lowerToken.equals("may"))
{
if (optAttrs.isEmpty())
{
pos = skipSpaces(objectClassString, pos, length);
pos = readOIDs(objectClassString, pos, length, optAttrs);
}
else
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_OC_DECODE_MULTIPLE_ELEMENTS.get(
objectClassString, "MAY"));
}
}
else if (lowerToken.startsWith("x-"))
{
pos = skipSpaces(objectClassString, pos, length);
final ArrayList<String> valueList = new ArrayList<String>();
pos = readQDStrings(objectClassString, pos, length, valueList);
final String[] values = new String[valueList.size()];
valueList.toArray(values);
if (exts.containsKey(token))
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_OC_DECODE_DUP_EXT.get(objectClassString,
token));
}
exts.put(token, values);
}
else
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_OC_DECODE_UNEXPECTED_TOKEN.get(
objectClassString, token));
}
}
description = descr;
names = new String[nameList.size()];
nameList.toArray(names);
superiorClasses = new String[supList.size()];
supList.toArray(superiorClasses);
requiredAttributes = new String[reqAttrs.size()];
reqAttrs.toArray(requiredAttributes);
optionalAttributes = new String[optAttrs.size()];
optAttrs.toArray(optionalAttributes);
isObsolete = (obsolete != null);
objectClassType = ocType;
extensions = Collections.unmodifiableMap(exts);
}
public ObjectClassDefinition(final String oid, final String[] names,
final String description,
final boolean isObsolete,
final String[] superiorClasses,
final ObjectClassType objectClassType,
final String[] requiredAttributes,
final String[] optionalAttributes,
final Map<String,String[]> extensions)
{
ensureNotNull(oid);
this.oid = oid;
this.isObsolete = isObsolete;
this.description = description;
this.objectClassType = objectClassType;
if (names == null)
{
this.names = NO_STRINGS;
}
else
{
this.names = names;
}
if (superiorClasses == null)
{
this.superiorClasses = NO_STRINGS;
}
else
{
this.superiorClasses = superiorClasses;
}
if (requiredAttributes == null)
{
this.requiredAttributes = NO_STRINGS;
}
else
{
this.requiredAttributes = requiredAttributes;
}
if (optionalAttributes == null)
{
this.optionalAttributes = NO_STRINGS;
}
else
{
this.optionalAttributes = optionalAttributes;
}
if (extensions == null)
{
this.extensions = Collections.emptyMap();
}
else
{
this.extensions = Collections.unmodifiableMap(extensions);
}
final StringBuilder buffer = new StringBuilder();
createDefinitionString(buffer);
objectClassString = buffer.toString();
}
private void createDefinitionString(final StringBuilder buffer)
{
buffer.append("( ");
buffer.append(oid);
if (names.length == 1)
{
buffer.append(" NAME '");
buffer.append(names[0]);
buffer.append('\'');
}
else if (names.length > 1)
{
buffer.append(" NAME (");
for (final String name : names)
{
buffer.append(" '");
buffer.append(name);
buffer.append('\'');
}
buffer.append(" )");
}
if (description != null)
{
buffer.append(" DESC '");
encodeValue(description, buffer);
buffer.append('\'');
}
if (isObsolete)
{
buffer.append(" OBSOLETE");
}
if (superiorClasses.length == 1)
{
buffer.append(" SUP ");
buffer.append(superiorClasses[0]);
}
else if (superiorClasses.length > 1)
{
buffer.append(" SUP (");
for (int i=0; i < superiorClasses.length; i++)
{
if (i > 0)
{
buffer.append(" $ ");
}
else
{
buffer.append(' ');
}
buffer.append(superiorClasses[i]);
}
buffer.append(" )");
}
if (objectClassType != null)
{
buffer.append(' ');
buffer.append(objectClassType.getName());
}
if (requiredAttributes.length == 1)
{
buffer.append(" MUST ");
buffer.append(requiredAttributes[0]);
}
else if (requiredAttributes.length > 1)
{
buffer.append(" MUST (");
for (int i=0; i < requiredAttributes.length; i++)
{
if (i >0)
{
buffer.append(" $ ");
}
else
{
buffer.append(' ');
}
buffer.append(requiredAttributes[i]);
}
buffer.append(" )");
}
if (optionalAttributes.length == 1)
{
buffer.append(" MAY ");
buffer.append(optionalAttributes[0]);
}
else if (optionalAttributes.length > 1)
{
buffer.append(" MAY (");
for (int i=0; i < optionalAttributes.length; i++)
{
if (i > 0)
{
buffer.append(" $ ");
}
else
{
buffer.append(' ');
}
buffer.append(optionalAttributes[i]);
}
buffer.append(" )");
}
for (final Map.Entry<String,String[]> e : extensions.entrySet())
{
final String name = e.getKey();
final String[] values = e.getValue();
if (values.length == 1)
{
buffer.append(' ');
buffer.append(name);
buffer.append(" '");
encodeValue(values[0], buffer);
buffer.append('\'');
}
else
{
buffer.append(' ');
buffer.append(name);
buffer.append(" (");
for (final String value : values)
{
buffer.append(" '");
encodeValue(value, buffer);
buffer.append('\'');
}
buffer.append(" )");
}
}
buffer.append(" )");
}
public String getOID()
{
return oid;
}
public String[] getNames()
{
return names;
}
public String getNameOrOID()
{
if (names.length == 0)
{
return oid;
}
else
{
return names[0];
}
}
public boolean hasNameOrOID(final String s)
{
for (final String name : names)
{
if (s.equalsIgnoreCase(name))
{
return true;
}
}
return s.equalsIgnoreCase(oid);
}
public String getDescription()
{
return description;
}
public boolean isObsolete()
{
return isObsolete;
}
public String[] getSuperiorClasses()
{
return superiorClasses;
}
public Set<ObjectClassDefinition> getSuperiorClasses(final Schema schema,
final boolean recursive)
{
final LinkedHashSet<ObjectClassDefinition> ocSet =
new LinkedHashSet<ObjectClassDefinition>();
for (final String s : superiorClasses)
{
final ObjectClassDefinition d = schema.getObjectClass(s);
if (d != null)
{
ocSet.add(d);
if (recursive)
{
getSuperiorClasses(schema, d, ocSet);
}
}
}
return Collections.unmodifiableSet(ocSet);
}
private static void getSuperiorClasses(final Schema schema,
final ObjectClassDefinition oc,
final Set<ObjectClassDefinition> ocSet)
{
for (final String s : oc.superiorClasses)
{
final ObjectClassDefinition d = schema.getObjectClass(s);
if (d != null)
{
ocSet.add(d);
getSuperiorClasses(schema, d, ocSet);
}
}
}
public ObjectClassType getObjectClassType()
{
return objectClassType;
}
public ObjectClassType getObjectClassType(final Schema schema)
{
if (objectClassType != null)
{
return objectClassType;
}
for (final String ocName : superiorClasses)
{
final ObjectClassDefinition d = schema.getObjectClass(ocName);
if (d != null)
{
return d.getObjectClassType(schema);
}
}
return ObjectClassType.STRUCTURAL;
}
public String[] getRequiredAttributes()
{
return requiredAttributes;
}
public Set<AttributeTypeDefinition> getRequiredAttributes(final Schema schema,
final boolean includeSuperiorClasses)
{
final HashSet<AttributeTypeDefinition> attrSet =
new HashSet<AttributeTypeDefinition>();
for (final String s : requiredAttributes)
{
final AttributeTypeDefinition d = schema.getAttributeType(s);
if (d != null)
{
attrSet.add(d);
}
}
if (includeSuperiorClasses)
{
for (final String s : superiorClasses)
{
final ObjectClassDefinition d = schema.getObjectClass(s);
if (d != null)
{
getSuperiorRequiredAttributes(schema, d, attrSet);
}
}
}
return Collections.unmodifiableSet(attrSet);
}
private static void getSuperiorRequiredAttributes(final Schema schema,
final ObjectClassDefinition oc,
final Set<AttributeTypeDefinition> attrSet)
{
for (final String s : oc.requiredAttributes)
{
final AttributeTypeDefinition d = schema.getAttributeType(s);
if (d != null)
{
attrSet.add(d);
}
}
for (final String s : oc.superiorClasses)
{
final ObjectClassDefinition d = schema.getObjectClass(s);
getSuperiorRequiredAttributes(schema, d, attrSet);
}
}
public String[] getOptionalAttributes()
{
return optionalAttributes;
}
public Set<AttributeTypeDefinition> getOptionalAttributes(final Schema schema,
final boolean includeSuperiorClasses)
{
final HashSet<AttributeTypeDefinition> attrSet =
new HashSet<AttributeTypeDefinition>();
for (final String s : optionalAttributes)
{
final AttributeTypeDefinition d = schema.getAttributeType(s);
if (d != null)
{
attrSet.add(d);
}
}
if (includeSuperiorClasses)
{
final Set<AttributeTypeDefinition> requiredAttrs =
getRequiredAttributes(schema, true);
for (final AttributeTypeDefinition d : requiredAttrs)
{
attrSet.remove(d);
}
for (final String s : superiorClasses)
{
final ObjectClassDefinition d = schema.getObjectClass(s);
if (d != null)
{
getSuperiorOptionalAttributes(schema, d, attrSet, requiredAttrs);
}
}
}
return Collections.unmodifiableSet(attrSet);
}
private static void getSuperiorOptionalAttributes(final Schema schema,
final ObjectClassDefinition oc,
final Set<AttributeTypeDefinition> attrSet,
final Set<AttributeTypeDefinition> requiredSet)
{
for (final String s : oc.optionalAttributes)
{
final AttributeTypeDefinition d = schema.getAttributeType(s);
if ((d != null) && (! requiredSet.contains(d)))
{
attrSet.add(d);
}
}
for (final String s : oc.superiorClasses)
{
final ObjectClassDefinition d = schema.getObjectClass(s);
getSuperiorOptionalAttributes(schema, d, attrSet, requiredSet);
}
}
public Map<String,String[]> getExtensions()
{
return extensions;
}
@Override()
public int hashCode()
{
return oid.hashCode();
}
@Override()
public boolean equals(final Object o)
{
if (o == null)
{
return false;
}
if (o == this)
{
return true;
}
if (! (o instanceof ObjectClassDefinition))
{
return false;
}
final ObjectClassDefinition d = (ObjectClassDefinition) o;
return (oid.equals(d.oid) &&
stringsEqualIgnoreCaseOrderIndependent(names, d.names) &&
stringsEqualIgnoreCaseOrderIndependent(requiredAttributes,
d.requiredAttributes) &&
stringsEqualIgnoreCaseOrderIndependent(optionalAttributes,
d.optionalAttributes) &&
stringsEqualIgnoreCaseOrderIndependent(superiorClasses,
d.superiorClasses) &&
bothNullOrEqual(objectClassType, d.objectClassType) &&
bothNullOrEqualIgnoreCase(description, d.description) &&
(isObsolete == d.isObsolete) &&
extensionsEqual(extensions, d.extensions));
}
@Override()
public String toString()
{
return objectClassString;
}
}