/* * 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 2009 Sun Microsystems, Inc. */ package org.opends.sdk; import static com.sun.opends.sdk.messages.Messages.*; import static com.sun.opends.sdk.util.StaticUtils.*; import java.util.*; import org.opends.sdk.schema.AttributeType; import org.opends.sdk.schema.Schema; import org.opends.sdk.schema.UnknownSchemaElementException; import com.sun.opends.sdk.util.*; /** * An attribute description as defined in RFC 4512 section 2.5. * Attribute descriptions are used to identify an attribute in an entry * and are composed of an attribute type and a set of zero or more * attribute options. * * @see RFC * 4512 - Lightweight Directory Access Protocol (LDAP): Directory * Information Models */ public final class AttributeDescription implements Comparable { private static abstract class Impl implements Iterable { protected Impl() { // Nothing to do. } public abstract int compareTo(Impl other); public abstract boolean containsOption(String normalizedOption); public abstract boolean equals(Impl other); public abstract String firstNormalizedOption(); @Override public abstract int hashCode(); public abstract boolean hasOptions(); public abstract boolean isSubTypeOf(Impl other); public abstract boolean isSuperTypeOf(Impl other); public abstract int size(); } private static final class MultiOptionImpl extends Impl { private final String[] normalizedOptions; private final String[] options; private MultiOptionImpl(String[] options, String[] normalizedOptions) { if (normalizedOptions.length < 2) { throw new AssertionError(); } this.options = options; this.normalizedOptions = normalizedOptions; } @Override public int compareTo(Impl other) { final int thisSize = normalizedOptions.length; final int otherSize = other.size(); if (thisSize < otherSize) { return -1; } else if (thisSize > otherSize) { return 1; } else { // Same number of options. final MultiOptionImpl otherImpl = (MultiOptionImpl) other; for (int i = 0; i < thisSize; i++) { final String o1 = normalizedOptions[i]; final String o2 = otherImpl.normalizedOptions[i]; final int result = o1.compareTo(o2); if (result != 0) { return result; } } // All options the same. return 0; } } @Override public boolean containsOption(String normalizedOption) { final int sz = normalizedOptions.length; for (int i = 0; i < sz; i++) { if (normalizedOptions[i].equals(normalizedOption)) { return true; } } return false; } @Override public boolean equals(Impl other) { if (other instanceof MultiOptionImpl) { final MultiOptionImpl tmp = (MultiOptionImpl) other; return Arrays.equals(normalizedOptions, tmp.normalizedOptions); } else { return false; } } @Override public String firstNormalizedOption() { return normalizedOptions[0]; } @Override public int hashCode() { return Arrays.hashCode(normalizedOptions); } @Override public boolean hasOptions() { return true; } @Override public boolean isSubTypeOf(Impl other) { // Must contain a super-set of other's options. if (other == ZERO_OPTION_IMPL) { return true; } else if (other.size() == 1) { return containsOption(other.firstNormalizedOption()); } else if (other.size() > size()) { return false; } else { // Check this contains other's options. // // This could be optimized more if required, but it's probably // not worth it. final MultiOptionImpl tmp = (MultiOptionImpl) other; for (final String normalizedOption : tmp.normalizedOptions) { if (!containsOption(normalizedOption)) { return false; } } return true; } } @Override public boolean isSuperTypeOf(Impl other) { // Must contain a sub-set of other's options. for (final String normalizedOption : normalizedOptions) { if (!other.containsOption(normalizedOption)) { return false; } } return true; } public Iterator iterator() { return Iterators.arrayIterator(options); } @Override public int size() { return normalizedOptions.length; } } private static final class SingleOptionImpl extends Impl { private final String normalizedOption; private final String option; private SingleOptionImpl(String option, String normalizedOption) { this.option = option; this.normalizedOption = normalizedOption; } @Override public int compareTo(Impl other) { if (other == ZERO_OPTION_IMPL) { // If other has zero options then this sorts after. return 1; } else if (other.size() == 1) { // Same number of options, so compare. return normalizedOption .compareTo(other.firstNormalizedOption()); } else { // Other has more options, so comes after. return -1; } } @Override public boolean containsOption(String normalizedOption) { return this.normalizedOption.equals(normalizedOption); } @Override public boolean equals(Impl other) { return other.size() == 1 && other.containsOption(normalizedOption); } @Override public String firstNormalizedOption() { return normalizedOption; } @Override public int hashCode() { return normalizedOption.hashCode(); } @Override public boolean hasOptions() { return true; } @Override public boolean isSubTypeOf(Impl other) { // Other must have no options or the same option. if (other == ZERO_OPTION_IMPL) { return true; } else { return equals(other); } } @Override public boolean isSuperTypeOf(Impl other) { // Other must have this option. return other.containsOption(normalizedOption); } public Iterator iterator() { return Iterators.singleton(option); } @Override public int size() { return 1; } } private static final class ZeroOptionImpl extends Impl { private ZeroOptionImpl() { // Nothing to do. } @Override public int compareTo(Impl other) { // If other has options then this sorts before. return this == other ? 0 : -1; } @Override public boolean containsOption(String normalizedOption) { return false; } @Override public boolean equals(Impl other) { return this == other; } @Override public String firstNormalizedOption() { // No first option. return null; } @Override public int hashCode() { // Use attribute type hash code. return 0; } @Override public boolean hasOptions() { return false; } @Override public boolean isSubTypeOf(Impl other) { // Can only be a sub-type if other has no options. return this == other; } @Override public boolean isSuperTypeOf(Impl other) { // Will always be a super-type. return true; } public Iterator iterator() { return Iterators.empty(); } @Override public int size() { return 0; } } private static final ThreadLocal>> CACHE = new ThreadLocal>>() { /** * {@inheritDoc} */ @Override protected WeakHashMap> initialValue() { return new WeakHashMap>(); } }; // Object class attribute description. private static final ZeroOptionImpl ZERO_OPTION_IMPL = new ZeroOptionImpl(); private static final AttributeDescription OBJECT_CLASS; static { final AttributeType attributeType = Schema.getCoreSchema() .getAttributeType("2.5.4.0"); OBJECT_CLASS = new AttributeDescription(attributeType .getNameOrOID(), attributeType, ZERO_OPTION_IMPL); } // This is the size of the per-thread per-schema attribute description // cache. We should be conservative here in case there are many // threads. private static final int ATTRIBUTE_DESCRIPTION_CACHE_SIZE = 512; /** * Creates an attribute description having the same attribute type and * options as the provided attribute description and, in addition, the * provided list of options. * * @param attributeDescription * The attribute description. * @param options * The attribute options. * @return The new attribute description containing {@code options}. * @throws NullPointerException * If {@code attributeDescription} or {@code options} was * {@code null}. */ public static AttributeDescription create( AttributeDescription attributeDescription, String... options) throws NullPointerException { Validator.ensureNotNull(attributeDescription, options); // This should not be called very often, so don't optimize. AttributeDescription newAttributeDescription = attributeDescription; for (final String option : options) { newAttributeDescription = create(newAttributeDescription, option); } return newAttributeDescription; } /** * Creates an attribute description having the same attribute type and * options as the provided attribute description and, in addition, the * provided new option. * * @param attributeDescription * The attribute description. * @param option * The attribute option. * @return The new attribute description containing {@code option}. * @throws NullPointerException * If {@code attributeDescription} or {@code option} was * {@code null}. */ public static AttributeDescription create( AttributeDescription attributeDescription, String option) throws NullPointerException { Validator.ensureNotNull(attributeDescription, option); final String normalizedOption = toLowerCase(option); if (attributeDescription.pimpl.containsOption(normalizedOption)) { return attributeDescription; } final String oldAttributeDescription = attributeDescription.attributeDescription; final StringBuilder builder = new StringBuilder( oldAttributeDescription.length() + option.length() + 1); builder.append(oldAttributeDescription); builder.append(';'); builder.append(option); final String newAttributeDescription = builder.toString(); final Impl impl = attributeDescription.pimpl; if (impl instanceof ZeroOptionImpl) { return new AttributeDescription(newAttributeDescription, attributeDescription.attributeType, new SingleOptionImpl( option, normalizedOption)); } else if (impl instanceof SingleOptionImpl) { final SingleOptionImpl simpl = (SingleOptionImpl) impl; final String[] newOptions = new String[2]; newOptions[0] = simpl.option; newOptions[1] = option; final String[] newNormalizedOptions = new String[2]; if (normalizedOption.compareTo(simpl.normalizedOption) < 0) { newNormalizedOptions[0] = normalizedOption; newNormalizedOptions[1] = simpl.normalizedOption; } return new AttributeDescription(newAttributeDescription, attributeDescription.attributeType, new MultiOptionImpl( newOptions, newNormalizedOptions)); } else { final MultiOptionImpl mimpl = (MultiOptionImpl) impl; final int sz1 = mimpl.options.length; final String[] newOptions = new String[sz1 + 1]; for (int i = 0; i < sz1; i++) { newOptions[i] = mimpl.options[i]; } newOptions[sz1] = option; final int sz2 = mimpl.normalizedOptions.length; final String[] newNormalizedOptions = new String[sz2 + 1]; boolean inserted = false; for (int i = 0; i < sz2; i++) { if (!inserted) { final String s = mimpl.normalizedOptions[i]; if (normalizedOption.compareTo(s) < 0) { newNormalizedOptions[i] = normalizedOption; newNormalizedOptions[i + 1] = s; inserted = true; } else { newNormalizedOptions[i] = s; } } else { newNormalizedOptions[i + 1] = mimpl.normalizedOptions[i]; } } if (!inserted) { newNormalizedOptions[sz2] = normalizedOption; } return new AttributeDescription(newAttributeDescription, attributeDescription.attributeType, new MultiOptionImpl( newOptions, newNormalizedOptions)); } } /** * Creates an attribute description having the provided attribute type * and no options. * * @param attributeType * The attribute type. * @return The attribute description. * @throws NullPointerException * If {@code attributeType} was {@code null}. */ public static AttributeDescription create(AttributeType attributeType) throws NullPointerException { Validator.ensureNotNull(attributeType); // Use object identity in case attribute type does not come from // core schema. if (attributeType == OBJECT_CLASS.getAttributeType()) { return OBJECT_CLASS; } else { return new AttributeDescription(attributeType.getNameOrOID(), attributeType, ZERO_OPTION_IMPL); } } /** * Creates an attribute description having the provided attribute type * and single option. * * @param attributeType * The attribute type. * @param option * The attribute option. * @return The attribute description. * @throws NullPointerException * If {@code attributeType} or {@code option} was {@code * null}. */ public static AttributeDescription create( AttributeType attributeType, String option) throws NullPointerException { Validator.ensureNotNull(attributeType, option); final String oid = attributeType.getNameOrOID(); final StringBuilder builder = new StringBuilder(oid.length() + option.length() + 1); builder.append(oid); builder.append(';'); builder.append(option); final String attributeDescription = builder.toString(); final String normalizedOption = toLowerCase(option); return new AttributeDescription(attributeDescription, attributeType, new SingleOptionImpl(option, normalizedOption)); } /** * Creates an attribute description having the provided attribute type * and options. * * @param attributeType * The attribute type. * @param options * The attribute options. * @return The attribute description. * @throws NullPointerException * If {@code attributeType} or {@code options} was {@code * null}. */ public static AttributeDescription create( AttributeType attributeType, String... options) throws NullPointerException { Validator.ensureNotNull(attributeType, options); switch (options.length) { case 0: return create(attributeType); case 1: return create(attributeType, options[0]); default: final String[] optionsList = new String[options.length]; final String[] normalizedOptions = new String[options.length]; final String oid = attributeType.getNameOrOID(); final StringBuilder builder = new StringBuilder(oid.length() + options[0].length() + options[1].length() + 2); builder.append(oid); int i = 0; for (final String option : options) { builder.append(';'); builder.append(option); optionsList[i] = option; final String normalizedOption = toLowerCase(option); normalizedOptions[i++] = normalizedOption; } Arrays.sort(normalizedOptions); final String attributeDescription = builder.toString(); return new AttributeDescription(attributeDescription, attributeType, new MultiOptionImpl(optionsList, normalizedOptions)); } } /** * Returns an attribute description representing the object class * attribute type with no options. * * @return The object class attribute description. */ public static AttributeDescription objectClass() { return OBJECT_CLASS; } /** * Parses the provided LDAP string representation of an attribute * description using the default schema. * * @param attributeDescription * The LDAP string representation of an attribute * description. * @return The parsed attribute description. * @throws LocalizedIllegalArgumentException * If {@code attributeDescription} is not a valid LDAP * string representation of an attribute description. * @throws NullPointerException * If {@code attributeDescription} was {@code null}. */ public static AttributeDescription valueOf(String attributeDescription) throws LocalizedIllegalArgumentException, NullPointerException { return valueOf(attributeDescription, Schema.getDefaultSchema()); } /** * Parses the provided LDAP string representation of an attribute * description using the provided schema. * * @param attributeDescription * The LDAP string representation of an attribute * description. * @param schema * The schema to use when parsing the attribute description. * @return The parsed attribute description. * @throws LocalizedIllegalArgumentException * If {@code attributeDescription} is not a valid LDAP * string representation of an attribute description. * @throws NullPointerException * If {@code attributeDescription} or {@code schema} was * {@code null}. */ @SuppressWarnings("serial") public static AttributeDescription valueOf( String attributeDescription, Schema schema) throws LocalizedIllegalArgumentException, NullPointerException { Validator.ensureNotNull(attributeDescription, schema); // First look up the attribute description in the cache. final WeakHashMap> threadLocalMap = CACHE .get(); Map schemaLocalMap = threadLocalMap .get(schema); AttributeDescription ad = null; if (schemaLocalMap == null) { schemaLocalMap = new LinkedHashMap( ATTRIBUTE_DESCRIPTION_CACHE_SIZE, 0.75f, true) { @Override protected boolean removeEldestEntry( Map.Entry eldest) { return size() > ATTRIBUTE_DESCRIPTION_CACHE_SIZE; } }; threadLocalMap.put(schema, schemaLocalMap); } else { ad = schemaLocalMap.get(attributeDescription); } // Cache miss: decode and cache. if (ad == null) { ad = valueOf0(attributeDescription, schema); schemaLocalMap.put(attributeDescription, ad); } return ad; } private static int skipTrailingWhiteSpace( String attributeDescription, int i, int length) throws LocalizedIllegalArgumentException { char c; while (i < length) { c = attributeDescription.charAt(i); if (c != ' ') { final LocalizableMessage message = ERR_ATTRIBUTE_DESCRIPTION_INTERNAL_WHITESPACE .get(attributeDescription); throw new LocalizedIllegalArgumentException(message); } i++; } return i; } // Uncached valueOf implementation. private static AttributeDescription valueOf0( String attributeDescription, Schema schema) throws LocalizedIllegalArgumentException { int i = 0; final int length = attributeDescription.length(); char c = 0; // Skip leading white space. while (i < length) { c = attributeDescription.charAt(i); if (c != ' ') { break; } i++; } // If we're already at the end then the attribute description only // contained whitespace. if (i == length) { final LocalizableMessage message = ERR_ATTRIBUTE_DESCRIPTION_EMPTY .get(attributeDescription); throw new LocalizedIllegalArgumentException(message); } // Validate the first non-whitespace character. ASCIICharProp cp = ASCIICharProp.valueOf(c); if (cp == null) { final LocalizableMessage message = ERR_ATTRIBUTE_DESCRIPTION_ILLEGAL_CHARACTER .get(attributeDescription, c, i); throw new LocalizedIllegalArgumentException(message); } // Mark the attribute type start position. final int attributeTypeStart = i; if (cp.isLetter()) { // Non-numeric OID: letter + zero or more keychars. i++; while (i < length) { c = attributeDescription.charAt(i); if (c == ';' || c == ' ') { break; } cp = ASCIICharProp.valueOf(c); if (!cp.isKeyChar()) { final LocalizableMessage message = ERR_ATTRIBUTE_DESCRIPTION_ILLEGAL_CHARACTER .get(attributeDescription, c, i); throw new LocalizedIllegalArgumentException(message); } i++; } // (charAt(i) == ';' || c == ' ' || i == length) } else if (cp.isDigit()) { // Numeric OID: decimal digit + zero or more dots or decimals. i++; while (i < length) { c = attributeDescription.charAt(i); if (c == ';' || c == ' ') { break; } cp = ASCIICharProp.valueOf(c); if (c != '.' && !cp.isDigit()) { final LocalizableMessage message = ERR_ATTRIBUTE_DESCRIPTION_ILLEGAL_CHARACTER .get(attributeDescription, c, i); throw new LocalizedIllegalArgumentException(message); } i++; } // (charAt(i) == ';' || charAt(i) == ' ' || i == length) } else { final LocalizableMessage message = ERR_ATTRIBUTE_DESCRIPTION_ILLEGAL_CHARACTER .get(attributeDescription, c, i); throw new LocalizedIllegalArgumentException(message); } // Skip trailing white space. final int attributeTypeEnd = i; if (c == ' ') { i = skipTrailingWhiteSpace(attributeDescription, i + 1, length); } // Determine the portion of the string containing the attribute type // name. String oid; if (attributeTypeStart == 0 && attributeTypeEnd == length) { oid = attributeDescription; } else { oid = attributeDescription.substring(attributeTypeStart, attributeTypeEnd); } if (oid.length() == 0) { final LocalizableMessage message = ERR_ATTRIBUTE_DESCRIPTION_NO_TYPE .get(attributeDescription); throw new LocalizedIllegalArgumentException(message); } // Get the attribute type from the schema. AttributeType attributeType; try { attributeType = schema.getAttributeType(oid); } catch (final UnknownSchemaElementException e) { final LocalizableMessage message = ERR_ATTRIBUTE_DESCRIPTION_TYPE_NOT_FOUND .get(attributeDescription, e.getMessageObject()); throw new LocalizedIllegalArgumentException(message); } // If we're already at the end of the attribute description then it // does not contain any options. if (i == length) { // Use object identity in case attribute type does not come from // core schema. if (attributeType == OBJECT_CLASS.getAttributeType() && attributeDescription.equals(OBJECT_CLASS.toString())) { return OBJECT_CLASS; } else { return new AttributeDescription(attributeDescription, attributeType, ZERO_OPTION_IMPL); } } // At this point 'i' must point at a semi-colon. i++; StringBuilder builder = null; int optionStart = i; while (i < length) { c = attributeDescription.charAt(i); if (c == ' ' || c == ';') { break; } cp = ASCIICharProp.valueOf(c); if (!cp.isKeyChar()) { final LocalizableMessage message = ERR_ATTRIBUTE_DESCRIPTION_ILLEGAL_CHARACTER .get(attributeDescription, c, i); throw new LocalizedIllegalArgumentException(message); } if (builder == null) { if (cp.isUpperCase()) { // Need to normalize the option. builder = new StringBuilder(length - optionStart); builder.append(attributeDescription, optionStart, i); builder.append(cp.toLowerCase()); } } else { builder.append(cp.toLowerCase()); } i++; } String option = attributeDescription.substring(optionStart, i); String normalizedOption; if (builder != null) { normalizedOption = builder.toString(); } else { normalizedOption = option; } if (option.length() == 0) { final LocalizableMessage message = ERR_ATTRIBUTE_DESCRIPTION_EMPTY_OPTION .get(attributeDescription); throw new LocalizedIllegalArgumentException(message); } // Skip trailing white space. if (c == ' ') { i = skipTrailingWhiteSpace(attributeDescription, i + 1, length); } // If we're already at the end of the attribute description then it // only contains a single option. if (i == length) { return new AttributeDescription(attributeDescription, attributeType, new SingleOptionImpl(option, normalizedOption)); } // Multiple options need sorting and duplicates removed - we could // optimize a bit further here for 2 option attribute descriptions. final List options = new LinkedList(); options.add(option); final SortedSet normalizedOptions = new TreeSet(); normalizedOptions.add(normalizedOption); while (i < length) { // At this point 'i' must point at a semi-colon. i++; builder = null; optionStart = i; while (i < length) { c = attributeDescription.charAt(i); if (c == ' ' || c == ';') { break; } cp = ASCIICharProp.valueOf(c); if (!cp.isKeyChar()) { final LocalizableMessage message = ERR_ATTRIBUTE_DESCRIPTION_ILLEGAL_CHARACTER .get(attributeDescription, c, i); throw new LocalizedIllegalArgumentException(message); } if (builder == null) { if (cp.isUpperCase()) { // Need to normalize the option. builder = new StringBuilder(length - optionStart); builder.append(attributeDescription, optionStart, i); builder.append(cp.toLowerCase()); } } else { builder.append(cp.toLowerCase()); } i++; } option = attributeDescription.substring(optionStart, i); if (builder != null) { normalizedOption = builder.toString(); } else { normalizedOption = option; } if (option.length() == 0) { final LocalizableMessage message = ERR_ATTRIBUTE_DESCRIPTION_EMPTY_OPTION .get(attributeDescription); throw new LocalizedIllegalArgumentException(message); } // Skip trailing white space. if (c == ' ') { i = skipTrailingWhiteSpace(attributeDescription, i + 1, length); } options.add(option); normalizedOptions.add(normalizedOption); } return new AttributeDescription(attributeDescription, attributeType, new MultiOptionImpl(options .toArray(new String[options.size()]), normalizedOptions .toArray(new String[normalizedOptions.size()]))); } private final String attributeDescription; private final AttributeType attributeType; private final Impl pimpl; // Private constructor. private AttributeDescription(String attributeDescription, AttributeType attributeType, Impl pimpl) { this.attributeDescription = attributeDescription; this.attributeType = attributeType; this.pimpl = pimpl; } /** * Compares this attribute description to the provided attribute * description. The attribute types are compared first and then, if * equal, the options are normalized, sorted, and compared. * * @param other * The attribute description to be compared. * @return A negative integer, zero, or a positive integer as this * attribute description is less than, equal to, or greater * than the specified attribute description. * @throws NullPointerException * If {@code name} was {@code null}. */ public int compareTo(AttributeDescription other) throws NullPointerException { final int result = attributeType.compareTo(other.attributeType); if (result != 0) { return result; } else { // Attribute type is the same, so compare options. return pimpl.compareTo(other.pimpl); } } /** * Indicates whether or not this attribute description contains the * provided option. * * @param option * The option for which to make the determination. * @return {@code true} if this attribute description has the provided * option, or {@code false} if not. * @throws NullPointerException * If {@code option} was {@code null}. */ public boolean containsOption(String option) throws NullPointerException { final String normalizedOption = toLowerCase(option); return pimpl.containsOption(normalizedOption); } /** * Indicates whether the provided object is an attribute description * which is equal to this attribute description. It will be considered * equal if the attribute type and normalized sorted list of options * are identical. * * @param o * The object for which to make the determination. * @return {@code true} if the provided object is an attribute * description that is equal to this attribute description, or * {@code false} if not. */ @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof AttributeDescription)) { return false; } final AttributeDescription other = (AttributeDescription) o; if (!attributeType.equals(other.attributeType)) { return false; } // Attribute type is the same, compare options. return pimpl.equals(other.pimpl); } /** * Returns the attribute type associated with this attribute * description. * * @return The attribute type associated with this attribute * description. */ public AttributeType getAttributeType() { return attributeType; } /** * Returns an {@code Iterable} containing the options contained in * this attribute description. Attempts to remove options using an * iterator's {@code remove()} method are not permitted and will * result in an {@code UnsupportedOperationException} being thrown. * * @return An {@code Iterable} containing the options. */ public Iterable getOptions() { return pimpl; } /** * Returns the hash code for this attribute description. It will be * calculated as the sum of the hash codes of the attribute type and * normalized sorted list of options. * * @return The hash code for this attribute description. */ @Override public int hashCode() { // FIXME: should we cache this? return attributeType.hashCode() * 31 + pimpl.hashCode(); } /** * Indicates whether or not this attribute description has any * options. * * @return {@code true} if this attribute description has any options, * or {@code false} if not. */ public boolean hasOptions() { return pimpl.hasOptions(); } /** * Indicates whether or not this attribute description is the {@code * objectClass} attribute description with no options. * * @return {@code true} if this attribute description is the {@code * objectClass} attribute description with no options, or * {@code false} if not. */ public boolean isObjectClass() { return attributeType.isObjectClass() && !hasOptions(); } /** * Indicates whether or not this attribute description is a sub-type * of the provided attribute description as defined in RFC 4512 * section 2.5. Specifically, this method will return {@code true} if * and only if the following conditions are both {@code true}: *
    *
  • This attribute description has an attribute type which is equal * to, or is a sub-type of, the attribute type in the provided * attribute description. *
  • This attribute description contains all of the options * contained in the provided attribute description. *
* Note that this method will return {@code true} if this attribute * description is equal to the provided attribute description. * * @param other * The attribute description for which to make the * determination. * @return {@code true} if this attribute description is a sub-type of * the provided attribute description, or {@code false} if * not. * @throws NullPointerException * If {@code name} was {@code null}. */ public boolean isSubTypeOf(AttributeDescription other) throws NullPointerException { if (!attributeType.isSubTypeOf(other.attributeType)) { return false; } else { return pimpl.isSubTypeOf(other.pimpl); } } /** * Indicates whether or not this attribute description is a super-type * of the provided attribute description as defined in RFC 4512 * section 2.5. Specifically, this method will return {@code true} if * and only if the following conditions are both {@code true}: *
    *
  • This attribute description has an attribute type which is equal * to, or is a super-type of, the attribute type in the provided * attribute description. *
  • This attribute description contains a sub-set of the options * contained in the provided attribute description. *
* Note that this method will return {@code true} if this attribute * description is equal to the provided attribute description. * * @param other * The attribute description for which to make the * determination. * @return {@code true} if this attribute description is a super-type * of the provided attribute description, or {@code false} if * not. * @throws NullPointerException * If {@code name} was {@code null}. */ public boolean isSuperTypeOf(AttributeDescription other) throws NullPointerException { if (!other.attributeType.isSubTypeOf(attributeType)) { return false; } else { return pimpl.isSuperTypeOf(other.pimpl); } } /** * Returns the string representation of this attribute description as * defined in RFC4512 section 2.5. * * @return The string representation of this attribute description. */ @Override public String toString() { return attributeDescription; } }