All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.ldap.sdk.schema.Schema Maven / Gradle / Ivy

Go to download

The UnboundID LDAP SDK for Java is a fast, comprehensive, and easy-to-use Java API for communicating with LDAP directory servers and performing related tasks like reading and writing LDIF, encoding and decoding data using base64 and ASN.1 BER, and performing secure communication. This package contains the Standard Edition of the LDAP SDK, which is a complete, general-purpose library for communicating with LDAPv3 directory servers.

There is a newer version: 7.0.1
Show newest version
/*
 * Copyright 2007-2022 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright 2007-2022 Ping Identity Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * Copyright (C) 2007-2022 Ping Identity Corporation
 *
 * 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.schema;



import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.ReadOnlyEntry;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SearchScope;
import com.unboundid.ldif.LDIFException;
import com.unboundid.ldif.LDIFReader;
import com.unboundid.util.Debug;
import com.unboundid.util.NotMutable;
import com.unboundid.util.NotNull;
import com.unboundid.util.Nullable;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.Validator;

import static com.unboundid.ldap.sdk.schema.SchemaMessages.*;



/**
 * This class provides a data structure for representing a directory server
 * subschema subentry.  This includes information about the attribute syntaxes,
 * matching rules, attribute types, object classes, name forms, DIT content
 * rules, DIT structure rules, and matching rule uses defined in the server
 * schema.
 */
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class Schema
       implements Serializable
{
  /**
   * The name of the attribute used to hold the attribute syntax definitions.
   */
  @NotNull public static final String ATTR_ATTRIBUTE_SYNTAX = "ldapSyntaxes";



  /**
   * The name of the attribute used to hold the attribute type definitions.
   */
  @NotNull public static final String ATTR_ATTRIBUTE_TYPE = "attributeTypes";



  /**
   * The name of the attribute used to hold the DIT content rule definitions.
   */
  @NotNull public static final String ATTR_DIT_CONTENT_RULE = "dITContentRules";



  /**
   * The name of the attribute used to hold the DIT structure rule definitions.
   */
  @NotNull public static final String ATTR_DIT_STRUCTURE_RULE =
       "dITStructureRules";



  /**
   * The name of the attribute used to hold the matching rule definitions.
   */
  @NotNull public static final String ATTR_MATCHING_RULE = "matchingRules";



  /**
   * The name of the attribute used to hold the matching rule use definitions.
   */
  @NotNull public static final String ATTR_MATCHING_RULE_USE =
       "matchingRuleUse";



  /**
   * The name of the attribute used to hold the name form definitions.
   */
  @NotNull public static final String ATTR_NAME_FORM = "nameForms";



  /**
   * The name of the attribute used to hold the object class definitions.
   */
  @NotNull public static final String ATTR_OBJECT_CLASS = "objectClasses";



  /**
   * The name of the attribute used to hold the DN of the subschema subentry
   * with the schema information that governs a specified entry.
   */
  @NotNull public static final String ATTR_SUBSCHEMA_SUBENTRY =
       "subschemaSubentry";



  /**
   * The default standard schema available for use in the LDAP SDK.
   */
  @NotNull private static final AtomicReference
       DEFAULT_STANDARD_SCHEMA = new AtomicReference<>();



  /**
   * The set of request attributes that will be used when retrieving the server
   * subschema subentry in order to retrieve all of the schema elements.
   */
  @NotNull private static final String[] SCHEMA_REQUEST_ATTRS =
  {
    "*",
    ATTR_ATTRIBUTE_SYNTAX,
    ATTR_ATTRIBUTE_TYPE,
    ATTR_DIT_CONTENT_RULE,
    ATTR_DIT_STRUCTURE_RULE,
    ATTR_MATCHING_RULE,
    ATTR_MATCHING_RULE_USE,
    ATTR_NAME_FORM,
    ATTR_OBJECT_CLASS
  };



  /**
   * The set of request attributes that will be used when retrieving the
   * subschema subentry attribute from a specified entry in order to determine
   * the location of the server schema definitions.
   */
  @NotNull private static final String[] SUBSCHEMA_SUBENTRY_REQUEST_ATTRS =
  {
    ATTR_SUBSCHEMA_SUBENTRY
  };



  /**
   * Retrieves the resource path that may be used to obtain a file with a number
   * of standard schema definitions.
   */
  @NotNull private static final String DEFAULT_SCHEMA_RESOURCE_PATH =
       "com/unboundid/ldap/sdk/schema/standard-schema.ldif";



  /**
   * The serial version UID for this serializable class.
   */
  private static final long serialVersionUID = 8081839633831517925L;



  // A map of all subordinate attribute type definitions for each attribute
  // type definition.
  @NotNull private final
       Map>
            subordinateAttributeTypes;

  // The set of attribute syntaxes mapped from lowercase name/OID to syntax.
  @NotNull private final Map asMap;

  // The set of attribute types mapped from lowercase name/OID to type.
  @NotNull private final Map atMap;

  // The set of DIT content rules mapped from lowercase name/OID to rule.
  @NotNull private final Map dcrMap;

  // The set of DIT structure rules mapped from rule ID to rule.
  @NotNull private final Map dsrMapByID;

  // The set of DIT structure rules mapped from lowercase name to rule.
  @NotNull private final Map dsrMapByName;

  // The set of DIT structure rules mapped from lowercase name to rule.
  @NotNull private final Map
       dsrMapByNameForm;

  // The set of matching rules mapped from lowercase name/OID to rule.
  @NotNull private final Map mrMap;

  // The set of matching rule uses mapped from matching rule OID to use.
  @NotNull private final Map mruMap;

  // The set of name forms mapped from lowercase name/OID to name form.
  @NotNull private final Map nfMapByName;

  // The set of name forms mapped from structural class OID to name form.
  @NotNull private final Map nfMapByOC;

  // The set of object classes mapped from lowercase name/OID to class.
  @NotNull private final Map ocMap;

  // The entry used to create this schema object.
  @NotNull private final ReadOnlyEntry schemaEntry;

  // The set of attribute syntaxes defined in the schema.
  @NotNull private final Set asSet;

  // The set of attribute types defined in the schema.
  @NotNull private final Set atSet;

  // The set of operational attribute types defined in the schema.
  @NotNull private final Set operationalATSet;

  // The set of user attribute types defined in the schema.
  @NotNull private final Set userATSet;

  // The set of DIT content rules defined in the schema.
  @NotNull private final Set dcrSet;

  // The set of DIT structure rules defined in the schema.
  @NotNull private final Set dsrSet;

  // The set of matching rules defined in the schema.
  @NotNull private final Set mrSet;

  // The set of matching rule uses defined in the schema.
  @NotNull private final Set mruSet;

  // The set of name forms defined in the schema.
  @NotNull private final Set nfSet;

  // The set of object classes defined in the schema.
  @NotNull private final Set ocSet;

  // The set of abstract object classes defined in the schema.
  @NotNull private final Set abstractOCSet;

  // The set of auxiliary object classes defined in the schema.
  @NotNull private final Set auxiliaryOCSet;

  // The set of structural object classes defined in the schema.
  @NotNull private final Set structuralOCSet;



  /**
   * Creates a new schema object by decoding the information in the provided
   * entry.  Any schema elements that cannot be parsed will be silently ignored.
   *
   * @param  schemaEntry  The schema entry to decode.  It must not be
   *                      {@code null}.
   */
  public Schema(@NotNull final Entry schemaEntry)
  {
    this(schemaEntry, null, null, null, null, null, null, null, null);
  }



  /**
   * Creates a new schema object by decoding the information in the provided
   * entry, optionally capturing any information about unparsable values in the
   * provided maps.
   *
   * @param  schemaEntry                  The schema entry to decode.  It must
   *                                      not be {@code null}.
   * @param  unparsableAttributeSyntaxes  A map that will be updated with
   *                                      information about any attribute syntax
   *                                      definitions that cannot be parsed.  It
   *                                      may be {@code null} if unparsable
   *                                      attribute syntax definitions should be
   *                                      silently ignored.
   * @param  unparsableMatchingRules      A map that will be updated with
   *                                      information about any matching rule
   *                                      definitions that cannot be parsed.  It
   *                                      may be {@code null} if unparsable
   *                                      matching rule definitions should be
   *                                      silently ignored.
   * @param  unparsableAttributeTypes     A map that will be updated with
   *                                      information about any attribute type
   *                                      definitions that cannot be parsed.  It
   *                                      may be {@code null} if unparsable
   *                                      attribute type definitions should be
   *                                      silently ignored.
   * @param  unparsableObjectClasses      A map that will be updated with
   *                                      information about any object class
   *                                      definitions that cannot be parsed.  It
   *                                      may be {@code null} if unparsable
   *                                      object class definitions should be
   *                                      silently ignored.
   * @param  unparsableDITContentRules    A map that will be updated with
   *                                      information about any DIT content rule
   *                                      definitions that cannot be parsed.  It
   *                                      may be {@code null} if unparsable
   *                                      DIT content rule definitions should be
   *                                      silently ignored.
   * @param  unparsableDITStructureRules  A map that will be updated with
   *                                      information about any DIT structure
   *                                      rule definitions that cannot be
   *                                      parsed.  It may be {@code null} if
   *                                      unparsable attribute DIT structure
   *                                      rule definitions should be silently
   *                                      ignored.
   * @param  unparsableNameForms          A map that will be updated with
   *                                      information about any name form
   *                                      definitions that cannot be parsed.  It
   *                                      may be {@code null} if unparsable
   *                                      name form definitions should be
   *                                      silently ignored.
   * @param  unparsableMatchingRuleUses   A map that will be updated with
   *                                      information about any matching rule
   *                                      use definitions that cannot be parsed.
   *                                      It may be {@code null} if unparsable
   *                                      matching rule use definitions should
   *                                      be silently ignored.
   */
  public Schema(@NotNull final Entry schemaEntry,
       @Nullable final Map unparsableAttributeSyntaxes,
       @Nullable final Map unparsableMatchingRules,
       @Nullable final Map unparsableAttributeTypes,
       @Nullable final Map unparsableObjectClasses,
       @Nullable final Map unparsableDITContentRules,
       @Nullable final Map unparsableDITStructureRules,
       @Nullable final Map unparsableNameForms,
       @Nullable final Map unparsableMatchingRuleUses)
  {
    this.schemaEntry = new ReadOnlyEntry(schemaEntry);

    // Decode the attribute syntaxes from the schema entry.
    String[] defs = schemaEntry.getAttributeValues(ATTR_ATTRIBUTE_SYNTAX);
    if (defs == null)
    {
      asMap = Collections.emptyMap();
      asSet = Collections.emptySet();
    }
    else
    {
      final LinkedHashMap m =
           new LinkedHashMap<>(StaticUtils.computeMapCapacity(defs.length));
      final LinkedHashSet s =
           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));

      for (final String def : defs)
      {
        try
        {
          final AttributeSyntaxDefinition as =
               new AttributeSyntaxDefinition(def);
          s.add(as);
          m.put(StaticUtils.toLowerCase(as.getOID()), as);
        }
        catch (final LDAPException le)
        {
          Debug.debugException(le);
          if (unparsableAttributeSyntaxes != null)
          {
            unparsableAttributeSyntaxes.put(def, le);
          }
        }
      }

      asMap = Collections.unmodifiableMap(m);
      asSet = Collections.unmodifiableSet(s);
    }


    // Decode the attribute types from the schema entry.
    defs = schemaEntry.getAttributeValues(ATTR_ATTRIBUTE_TYPE);
    if (defs == null)
    {
      atMap            = Collections.emptyMap();
      atSet            = Collections.emptySet();
      operationalATSet = Collections.emptySet();
      userATSet        = Collections.emptySet();
    }
    else
    {
      final LinkedHashMap m =
           new LinkedHashMap<>(StaticUtils.computeMapCapacity(2*defs.length));
      final LinkedHashSet s =
           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
      final LinkedHashSet sUser =
           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
      final LinkedHashSet sOperational =
           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));

      for (final String def : defs)
      {
        try
        {
          final AttributeTypeDefinition at = new AttributeTypeDefinition(def);
          s.add(at);
          m.put(StaticUtils.toLowerCase(at.getOID()), at);
          for (final String name : at.getNames())
          {
            m.put(StaticUtils.toLowerCase(name), at);
          }

          if (at.isOperational())
          {
            sOperational.add(at);
          }
          else
          {
            sUser.add(at);
          }
        }
        catch (final LDAPException le)
        {
          Debug.debugException(le);
          if (unparsableAttributeTypes != null)
          {
            unparsableAttributeTypes.put(def, le);
          }
        }
      }

      atMap            = Collections.unmodifiableMap(m);
      atSet            = Collections.unmodifiableSet(s);
      operationalATSet = Collections.unmodifiableSet(sOperational);
      userATSet        = Collections.unmodifiableSet(sUser);
    }


    // Decode the DIT content rules from the schema entry.
    defs = schemaEntry.getAttributeValues(ATTR_DIT_CONTENT_RULE);
    if (defs == null)
    {
      dcrMap = Collections.emptyMap();
      dcrSet = Collections.emptySet();
    }
    else
    {
      final LinkedHashMap m =
           new LinkedHashMap<>(2*defs.length);
      final LinkedHashSet s =
           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));

      for (final String def : defs)
      {
        try
        {
          final DITContentRuleDefinition dcr =
               new DITContentRuleDefinition(def);
          s.add(dcr);
          m.put(StaticUtils.toLowerCase(dcr.getOID()), dcr);
          for (final String name : dcr.getNames())
          {
            m.put(StaticUtils.toLowerCase(name), dcr);
          }
        }
        catch (final LDAPException le)
        {
          Debug.debugException(le);
          if (unparsableDITContentRules != null)
          {
            unparsableDITContentRules.put(def, le);
          }
        }
      }

      dcrMap = Collections.unmodifiableMap(m);
      dcrSet = Collections.unmodifiableSet(s);
    }


    // Decode the DIT structure rules from the schema entry.
    defs = schemaEntry.getAttributeValues(ATTR_DIT_STRUCTURE_RULE);
    if (defs == null)
    {
      dsrMapByID       = Collections.emptyMap();
      dsrMapByName     = Collections.emptyMap();
      dsrMapByNameForm = Collections.emptyMap();
      dsrSet           = Collections.emptySet();
    }
    else
    {
      final LinkedHashMap mID =
           new LinkedHashMap<>(StaticUtils.computeMapCapacity(defs.length));
      final LinkedHashMap mN =
           new LinkedHashMap<>(StaticUtils.computeMapCapacity(defs.length));
      final LinkedHashMap mNF =
           new LinkedHashMap<>(StaticUtils.computeMapCapacity(defs.length));
      final LinkedHashSet s =
           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));

      for (final String def : defs)
      {
        try
        {
          final DITStructureRuleDefinition dsr =
               new DITStructureRuleDefinition(def);
          s.add(dsr);
          mID.put(dsr.getRuleID(), dsr);
          mNF.put(StaticUtils.toLowerCase(dsr.getNameFormID()), dsr);
          for (final String name : dsr.getNames())
          {
            mN.put(StaticUtils.toLowerCase(name), dsr);
          }
        }
        catch (final LDAPException le)
        {
          Debug.debugException(le);
          if (unparsableDITStructureRules != null)
          {
            unparsableDITStructureRules.put(def, le);
          }
        }
      }

      dsrMapByID       = Collections.unmodifiableMap(mID);
      dsrMapByName     = Collections.unmodifiableMap(mN);
      dsrMapByNameForm = Collections.unmodifiableMap(mNF);
      dsrSet           = Collections.unmodifiableSet(s);
    }


    // Decode the matching rules from the schema entry.
    defs = schemaEntry.getAttributeValues(ATTR_MATCHING_RULE);
    if (defs == null)
    {
      mrMap = Collections.emptyMap();
      mrSet = Collections.emptySet();
    }
    else
    {
      final LinkedHashMap m =
           new LinkedHashMap<>(StaticUtils.computeMapCapacity(2*defs.length));
      final LinkedHashSet s =
           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));

      for (final String def : defs)
      {
        try
        {
          final MatchingRuleDefinition mr = new MatchingRuleDefinition(def);
          s.add(mr);
          m.put(StaticUtils.toLowerCase(mr.getOID()), mr);
          for (final String name : mr.getNames())
          {
            m.put(StaticUtils.toLowerCase(name), mr);
          }
        }
        catch (final LDAPException le)
        {
          Debug.debugException(le);
          if (unparsableMatchingRules != null)
          {
            unparsableMatchingRules.put(def, le);
          }
        }
      }

      mrMap = Collections.unmodifiableMap(m);
      mrSet = Collections.unmodifiableSet(s);
    }


    // Decode the matching rule uses from the schema entry.
    defs = schemaEntry.getAttributeValues(ATTR_MATCHING_RULE_USE);
    if (defs == null)
    {
      mruMap = Collections.emptyMap();
      mruSet = Collections.emptySet();
    }
    else
    {
      final LinkedHashMap m =
           new LinkedHashMap<>(StaticUtils.computeMapCapacity(2*defs.length));
      final LinkedHashSet s =
           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));

      for (final String def : defs)
      {
        try
        {
          final MatchingRuleUseDefinition mru =
               new MatchingRuleUseDefinition(def);
          s.add(mru);
          m.put(StaticUtils.toLowerCase(mru.getOID()), mru);
          for (final String name : mru.getNames())
          {
            m.put(StaticUtils.toLowerCase(name), mru);
          }
        }
        catch (final LDAPException le)
        {
          Debug.debugException(le);
          if (unparsableMatchingRuleUses != null)
          {
            unparsableMatchingRuleUses.put(def, le);
          }
        }
      }

      mruMap = Collections.unmodifiableMap(m);
      mruSet = Collections.unmodifiableSet(s);
    }


    // Decode the name forms from the schema entry.
    defs = schemaEntry.getAttributeValues(ATTR_NAME_FORM);
    if (defs == null)
    {
      nfMapByName = Collections.emptyMap();
      nfMapByOC   = Collections.emptyMap();
      nfSet       = Collections.emptySet();
    }
    else
    {
      final LinkedHashMap mN =
           new LinkedHashMap<>(StaticUtils.computeMapCapacity(2*defs.length));
      final LinkedHashMap mOC =
           new LinkedHashMap<>(StaticUtils.computeMapCapacity(defs.length));
      final LinkedHashSet s =
           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));

      for (final String def : defs)
      {
        try
        {
          final NameFormDefinition nf = new NameFormDefinition(def);
          s.add(nf);
          mOC.put(StaticUtils.toLowerCase(nf.getStructuralClass()), nf);
          mN.put(StaticUtils.toLowerCase(nf.getOID()), nf);
          for (final String name : nf.getNames())
          {
            mN.put(StaticUtils.toLowerCase(name), nf);
          }
        }
        catch (final LDAPException le)
        {
          Debug.debugException(le);
          if(unparsableNameForms != null)
          {
            unparsableNameForms.put(def, le);
          }
        }
      }

      nfMapByName = Collections.unmodifiableMap(mN);
      nfMapByOC   = Collections.unmodifiableMap(mOC);
      nfSet       = Collections.unmodifiableSet(s);
    }


    // Decode the object classes from the schema entry.
    defs = schemaEntry.getAttributeValues(ATTR_OBJECT_CLASS);
    if (defs == null)
    {
      ocMap           = Collections.emptyMap();
      ocSet           = Collections.emptySet();
      abstractOCSet   = Collections.emptySet();
      auxiliaryOCSet  = Collections.emptySet();
      structuralOCSet = Collections.emptySet();
    }
    else
    {
      final LinkedHashMap m =
           new LinkedHashMap<>(StaticUtils.computeMapCapacity(2*defs.length));
      final LinkedHashSet s =
           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
      final LinkedHashSet sAbstract =
           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
      final LinkedHashSet sAuxiliary =
           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
      final LinkedHashSet sStructural =
           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));

      for (final String def : defs)
      {
        try
        {
          final ObjectClassDefinition oc = new ObjectClassDefinition(def);
          s.add(oc);
          m.put(StaticUtils.toLowerCase(oc.getOID()), oc);
          for (final String name : oc.getNames())
          {
            m.put(StaticUtils.toLowerCase(name), oc);
          }

          switch (oc.getObjectClassType(this))
          {
            case ABSTRACT:
              sAbstract.add(oc);
              break;
            case AUXILIARY:
              sAuxiliary.add(oc);
              break;
            case STRUCTURAL:
              sStructural.add(oc);
              break;
          }
        }
        catch (final LDAPException le)
        {
          Debug.debugException(le);
          if (unparsableObjectClasses != null)
          {
            unparsableObjectClasses.put(def, le);
          }
        }
      }

      ocMap           = Collections.unmodifiableMap(m);
      ocSet           = Collections.unmodifiableSet(s);
      abstractOCSet   = Collections.unmodifiableSet(sAbstract);
      auxiliaryOCSet  = Collections.unmodifiableSet(sAuxiliary);
      structuralOCSet = Collections.unmodifiableSet(sStructural);
    }


    // Populate the map of subordinate attribute types.
    final LinkedHashMap>
         subAttrTypes = new LinkedHashMap<>(
              StaticUtils.computeMapCapacity(atSet.size()));
    for (final AttributeTypeDefinition d : atSet)
    {
      AttributeTypeDefinition sup = d.getSuperiorType(this);
      while (sup != null)
      {
        List l = subAttrTypes.get(sup);
        if (l == null)
        {
          l = new ArrayList<>(1);
          subAttrTypes.put(sup, l);
        }
        l.add(d);

        sup = sup.getSuperiorType(this);
      }
    }
    subordinateAttributeTypes = Collections.unmodifiableMap(subAttrTypes);
  }



  /**
   * Parses all schema elements contained in the provided entry.  This method
   * differs from the {@link #Schema(Entry)} constructor in that this method
   * will throw an exception if it encounters any unparsable schema elements,
   * while the constructor will silently ignore them.  Alternately, the
   * 'constructor that takes a bunch of maps can be used to
   *
   * @param  schemaEntry  The schema entry to parse.  It must not be
   *                      {@code null}.
   *
   * @return  The schema entry that was parsed.
   *
   * @throws  LDAPException  If the provided entry contains any schema element
   *                         definitions that cannot be parsed.
   */
  @NotNull()
  public static Schema parseSchemaEntry(@NotNull final Entry schemaEntry)
         throws LDAPException
  {
    final Map unparsableAttributeSyntaxes =
         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
    final Map unparsableMatchingRules =
         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
    final Map unparsableAttributeTypes =
         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
    final Map unparsableObjectClasses =
         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
    final Map unparsableDITContentRules =
         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
    final Map unparsableDITStructureRules =
         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
    final Map unparsableNameForms =
         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
    final Map unparsableMatchingRuleUses =
         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));

    final Schema schema = new Schema(schemaEntry, unparsableAttributeSyntaxes,
         unparsableMatchingRules, unparsableAttributeTypes,
         unparsableObjectClasses, unparsableDITContentRules,
         unparsableDITStructureRules, unparsableNameForms,
         unparsableMatchingRuleUses);
    if (unparsableAttributeSyntaxes.isEmpty() &&
         unparsableMatchingRules.isEmpty() &&
         unparsableAttributeTypes.isEmpty() &&
         unparsableObjectClasses.isEmpty() &&
         unparsableDITContentRules.isEmpty() &&
         unparsableDITStructureRules.isEmpty() &&
         unparsableNameForms.isEmpty() &&
         unparsableMatchingRuleUses.isEmpty())
    {
      return schema;
    }

    final StringBuilder messageBuffer = new StringBuilder();
    for (final Map.Entry e :
         unparsableAttributeSyntaxes.entrySet())
    {
      appendErrorMessage(messageBuffer,
           ERR_SCHEMA_UNPARSABLE_AS.get(ATTR_ATTRIBUTE_SYNTAX, e.getKey(),
                StaticUtils.getExceptionMessage(e.getValue())));
    }

    for (final Map.Entry e :
         unparsableMatchingRules.entrySet())
    {
      appendErrorMessage(messageBuffer,
           ERR_SCHEMA_UNPARSABLE_MR.get(ATTR_MATCHING_RULE, e.getKey(),
                StaticUtils.getExceptionMessage(e.getValue())));
    }

    for (final Map.Entry e :
         unparsableAttributeTypes.entrySet())
    {
      appendErrorMessage(messageBuffer,
           ERR_SCHEMA_UNPARSABLE_AT.get(ATTR_ATTRIBUTE_TYPE, e.getKey(),
                StaticUtils.getExceptionMessage(e.getValue())));
    }

    for (final Map.Entry e :
         unparsableObjectClasses.entrySet())
    {
      appendErrorMessage(messageBuffer,
           ERR_SCHEMA_UNPARSABLE_OC.get(ATTR_OBJECT_CLASS, e.getKey(),
                StaticUtils.getExceptionMessage(e.getValue())));
    }

    for (final Map.Entry e :
         unparsableDITContentRules.entrySet())
    {
      appendErrorMessage(messageBuffer,
           ERR_SCHEMA_UNPARSABLE_DCR.get(ATTR_DIT_CONTENT_RULE, e.getKey(),
                StaticUtils.getExceptionMessage(e.getValue())));
    }

    for (final Map.Entry e :
         unparsableDITStructureRules.entrySet())
    {
      appendErrorMessage(messageBuffer,
           ERR_SCHEMA_UNPARSABLE_DSR.get(ATTR_DIT_STRUCTURE_RULE, e.getKey(),
                StaticUtils.getExceptionMessage(e.getValue())));
    }

    for (final Map.Entry e :
         unparsableNameForms.entrySet())
    {
      appendErrorMessage(messageBuffer,
           ERR_SCHEMA_UNPARSABLE_NF.get(ATTR_NAME_FORM, e.getKey(),
                StaticUtils.getExceptionMessage(e.getValue())));
    }

    for (final Map.Entry e :
         unparsableMatchingRuleUses.entrySet())
    {
      appendErrorMessage(messageBuffer,
           ERR_SCHEMA_UNPARSABLE_MRU.get(ATTR_MATCHING_RULE_USE, e.getKey(),
                StaticUtils.getExceptionMessage(e.getValue())));
    }

    throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
         messageBuffer.toString());
  }



  /**
   * Appends the provided message to the given buffer, adding spaces and
   * punctuation if necessary.
   *
   * @param  buffer   The buffer to which the message should be appended.
   * @param  message  The message to append to the buffer.
   */
  private static void appendErrorMessage(@NotNull final StringBuilder buffer,
                                         @NotNull final String message)
  {
    final int length = buffer.length();
    if (length > 0)
    {
      if (buffer.charAt(length - 1) == '.')
      {
        buffer.append("  ");
      }
      else
      {
        buffer.append(".  ");
      }
    }

    buffer.append(message);
  }



  /**
   * Retrieves the directory server schema over the provided connection.  The
   * root DSE will first be retrieved in order to get its subschemaSubentry DN,
   * and then that entry will be retrieved from the server and its contents
   * decoded as schema elements.  This should be sufficient for directories that
   * only provide a single schema, but for directories with multiple schemas it
   * may be necessary to specify the DN of an entry for which to retrieve the
   * subschema subentry.  Any unparsable schema elements will be silently
   * ignored.
   *
   * @param  connection  The connection to use in order to retrieve the server
   *                     schema.  It must not be {@code null}.
   *
   * @return  A decoded representation of the server schema.
   *
   * @throws  LDAPException  If a problem occurs while obtaining the server
   *                         schema.
   */
  @Nullable()
  public static Schema getSchema(@NotNull final LDAPConnection connection)
         throws LDAPException
  {
    return getSchema(connection, "");
  }



  /**
   * Retrieves the directory server schema that governs the specified entry.
   * In some servers, different portions of the DIT may be served by different
   * schemas, and in such cases it will be necessary to provide the DN of the
   * target entry in order to ensure that the appropriate schema which governs
   * that entry is returned.  For servers that support only a single schema,
   * any entry DN (including that of the root DSE) should be sufficient.  Any
   * unparsable schema elements will be silently ignored.
   *
   * @param  connection  The connection to use in order to retrieve the server
   *                     schema.  It must not be {@code null}.
   * @param  entryDN     The DN of the entry for which to retrieve the governing
   *                     schema.  It may be {@code null} or an empty string in
   *                     order to retrieve the schema that governs the server's
   *                     root DSE.
   *
   * @return  A decoded representation of the server schema, or {@code null} if
   *          it is not available for some reason (e.g., the client does not
   *          have permission to read the server schema).
   *
   * @throws  LDAPException  If a problem occurs while obtaining the server
   *                         schema.
   */
  @Nullable()
  public static Schema getSchema(@NotNull final LDAPConnection connection,
                                 @Nullable final String entryDN)
         throws LDAPException
  {
    return getSchema(connection, entryDN, false);
  }



  /**
   * Retrieves the directory server schema that governs the specified entry.
   * In some servers, different portions of the DIT may be served by different
   * schemas, and in such cases it will be necessary to provide the DN of the
   * target entry in order to ensure that the appropriate schema which governs
   * that entry is returned.  For servers that support only a single schema,
   * any entry DN (including that of the root DSE) should be sufficient.  This
   * method may optionally throw an exception if the retrieved schema contains
   * one or more unparsable schema elements.
   *
   * @param  connection                The connection to use in order to
   *                                   retrieve the server schema.  It must not
   *                                   be {@code null}.
   * @param  entryDN                   The DN of the entry for which to retrieve
   *                                   the governing schema.  It may be
   *                                   {@code null} or an empty string in order
   *                                   to retrieve the schema that governs the
   *                                   server's root DSE.
   * @param  throwOnUnparsableElement  Indicates whether to throw an exception
   *                                   if the schema entry that is retrieved has
   *                                   one or more unparsable schema elements.
   *
   * @return  A decoded representation of the server schema, or {@code null} if
   *          it is not available for some reason (e.g., the client does not
   *          have permission to read the server schema).
   *
   * @throws  LDAPException  If a problem occurs while obtaining the server
   *                         schema, or if the schema contains one or more
   *                         unparsable elements and
   *                         {@code throwOnUnparsableElement} is {@code true}.
   */
  @Nullable()
  public static Schema getSchema(@NotNull final LDAPConnection connection,
                                 @Nullable final String entryDN,
                                 final boolean throwOnUnparsableElement)
         throws LDAPException
  {
    Validator.ensureNotNull(connection);

    final String subschemaSubentryDN;
    if (entryDN == null)
    {
      subschemaSubentryDN = getSubschemaSubentryDN(connection, "");
    }
    else
    {
      subschemaSubentryDN = getSubschemaSubentryDN(connection, entryDN);
    }

    if (subschemaSubentryDN == null)
    {
      return null;
    }

    final Entry schemaEntry = connection.searchForEntry(subschemaSubentryDN,
         SearchScope.BASE,
         Filter.createEqualityFilter("objectClass", "subschema"),
         SCHEMA_REQUEST_ATTRS);
    if (schemaEntry == null)
    {
      return null;
    }

    if (throwOnUnparsableElement)
    {
      return parseSchemaEntry(schemaEntry);
    }
    else
    {
      return new Schema(schemaEntry);
    }
  }



  /**
   * Reads schema information from one or more files containing the schema
   * represented in LDIF form, with the definitions represented in the form
   * described in section 4.1 of RFC 4512.  Each file should contain a single
   * entry.  Any unparsable schema elements will be silently ignored.
   *
   * @param  schemaFiles  The paths to the LDIF files containing the schema
   *                      information to be read.  At least one file must be
   *                      specified.  If multiple files are specified, then they
   *                      will be processed in the order in which they have been
   *                      listed.
   *
   * @return  The schema read from the specified schema files, or {@code null}
   *          if none of the files contains any LDIF data to be read.
   *
   * @throws  IOException  If a problem occurs while attempting to read from
   *                       any of the specified files.
   *
   * @throws  LDIFException  If a problem occurs while attempting to parse the
   *                         contents of any of the schema files.
   */
  @Nullable()
  public static Schema getSchema(@NotNull final String... schemaFiles)
         throws IOException, LDIFException
  {
    Validator.ensureNotNull(schemaFiles);
    Validator.ensureFalse(schemaFiles.length == 0);

    final ArrayList files = new ArrayList<>(schemaFiles.length);
    for (final String s : schemaFiles)
    {
      files.add(new File(s));
    }

    return getSchema(files);
  }



  /**
   * Reads schema information from one or more files containing the schema
   * represented in LDIF form, with the definitions represented in the form
   * described in section 4.1 of RFC 4512.  Each file should contain a single
   * entry.  Any unparsable schema elements will be silently ignored.
   *
   * @param  schemaFiles  The paths to the LDIF files containing the schema
   *                      information to be read.  At least one file must be
   *                      specified.  If multiple files are specified, then they
   *                      will be processed in the order in which they have been
   *                      listed.
   *
   * @return  The schema read from the specified schema files, or {@code null}
   *          if none of the files contains any LDIF data to be read.
   *
   * @throws  IOException  If a problem occurs while attempting to read from
   *                       any of the specified files.
   *
   * @throws  LDIFException  If a problem occurs while attempting to parse the
   *                         contents of any of the schema files.
   */
  @Nullable()
  public static Schema getSchema(@NotNull final File... schemaFiles)
         throws IOException, LDIFException
  {
    Validator.ensureNotNull(schemaFiles);
    Validator.ensureFalse(schemaFiles.length == 0);

    return getSchema(Arrays.asList(schemaFiles));
  }



  /**
   * Reads schema information from one or more files containing the schema
   * represented in LDIF form, with the definitions represented in the form
   * described in section 4.1 of RFC 4512.  Each file should contain a single
   * entry.  Any unparsable schema elements will be silently ignored.
   *
   * @param  schemaFiles  The paths to the LDIF files containing the schema
   *                      information to be read.  At least one file must be
   *                      specified.  If multiple files are specified, then they
   *                      will be processed in the order in which they have been
   *                      listed.
   *
   * @return  The schema read from the specified schema files, or {@code null}
   *          if none of the files contains any LDIF data to be read.
   *
   * @throws  IOException  If a problem occurs while attempting to read from
   *                       any of the specified files.
   *
   * @throws  LDIFException  If a problem occurs while attempting to parse the
   *                         contents of any of the schema files.
   */
  @Nullable()
  public static Schema getSchema(@NotNull final List schemaFiles)
         throws IOException, LDIFException
  {
    return getSchema(schemaFiles, false);
  }



  /**
   * Reads schema information from one or more files containing the schema
   * represented in LDIF form, with the definitions represented in the form
   * described in section 4.1 of RFC 4512.  Each file should contain a single
   * entry.
   *
   * @param  schemaFiles               The paths to the LDIF files containing
   *                                   the schema information to be read.  At
   *                                   least one file must be specified.  If
   *                                   multiple files are specified, then they
   *                                   will be processed in the order in which
   *                                   they have been listed.
   * @param  throwOnUnparsableElement  Indicates whether to throw an exception
   *                                   if the schema entry that is retrieved has
   *                                   one or more unparsable schema elements.
   *
   * @return  The schema read from the specified schema files, or {@code null}
   *          if none of the files contains any LDIF data to be read.
   *
   * @throws  IOException  If a problem occurs while attempting to read from
   *                       any of the specified files.
   *
   * @throws  LDIFException  If a problem occurs while attempting to parse the
   *                         contents of any of the schema files.  If
   *                         {@code throwOnUnparsableElement} is {@code true},
   *                         then this may also be thrown if any of the schema
   *                         files contains any unparsable schema elements.
   */
  @Nullable()
  public static Schema getSchema(@NotNull final List schemaFiles,
                                 final boolean throwOnUnparsableElement)
         throws IOException, LDIFException
  {
    Validator.ensureNotNull(schemaFiles);
    Validator.ensureFalse(schemaFiles.isEmpty());

    Entry schemaEntry = null;
    for (final File f : schemaFiles)
    {
      final LDIFReader ldifReader = new LDIFReader(f);

      try
      {
        final Entry e = ldifReader.readEntry();
        if (e == null)
        {
          continue;
        }

        e.addAttribute("objectClass", "top", "ldapSubentry", "subschema");

        if (schemaEntry == null)
        {
          schemaEntry = e;
        }
        else
        {
          for (final Attribute a : e.getAttributes())
          {
            schemaEntry.addAttribute(a);
          }
        }
      }
      finally
      {
        ldifReader.close();
      }
    }

    if (schemaEntry == null)
    {
      return null;
    }

    if (throwOnUnparsableElement)
    {
      try
      {
        return parseSchemaEntry(schemaEntry);
      }
      catch (final LDAPException e)
      {
        Debug.debugException(e);
        throw new LDIFException(e.getMessage(), 0, false, e);
      }
    }
    else
    {
      return new Schema(schemaEntry);
    }
  }



  /**
   * Reads schema information from the provided input stream.  The information
   * should be in LDIF form, with the definitions represented in the form
   * described in section 4.1 of RFC 4512.  Only a single entry will be read
   * from the input stream, and it will be closed at the end of this method.
   *
   * @param  inputStream  The input stream from which the schema entry will be
   *                      read.  It must not be {@code null}, and it will be
   *                      closed when this method returns.
   *
   * @return  The schema read from the provided input stream, or {@code null} if
   *          the end of the input stream is reached without reading any data.
   *
   * @throws  IOException  If a problem is encountered while attempting to read
   *                       from the provided input stream.
   *
   * @throws  LDIFException  If a problem occurs while attempting to parse the
   *                         data read as LDIF.
   */
  @Nullable()
  public static Schema getSchema(@NotNull final InputStream inputStream)
         throws IOException, LDIFException
  {
    Validator.ensureNotNull(inputStream);

    final LDIFReader ldifReader = new LDIFReader(inputStream);

    try
    {
      final Entry e = ldifReader.readEntry();
      if (e == null)
      {
        return null;
      }
      else
      {
        return new Schema(e);
      }
    }
    finally
    {
      ldifReader.close();
    }
  }



  /**
   * Retrieves a schema object that contains definitions for a number of
   * standard attribute types and object classes from LDAP-related RFCs and
   * Internet Drafts.
   *
   * @return  A schema object that contains definitions for a number of standard
   *          attribute types and object classes from LDAP-related RFCs and
   *          Internet Drafts.
   *
   * @throws  LDAPException  If a problem occurs while attempting to obtain or
   *                         parse the default standard schema definitions.
   */
  @NotNull()
  public static Schema getDefaultStandardSchema()
         throws LDAPException
  {
    final Schema s = DEFAULT_STANDARD_SCHEMA.get();
    if (s != null)
    {
      return s;
    }

    synchronized (DEFAULT_STANDARD_SCHEMA)
    {
      try
      {
        final ClassLoader classLoader = Schema.class.getClassLoader();
        final InputStream inputStream =
             classLoader.getResourceAsStream(DEFAULT_SCHEMA_RESOURCE_PATH);
        final LDIFReader ldifReader = new LDIFReader(inputStream);
        final Entry schemaEntry = ldifReader.readEntry();
        ldifReader.close();

        final Schema schema = new Schema(schemaEntry);
        DEFAULT_STANDARD_SCHEMA.set(schema);
        return schema;
      }
      catch (final Exception e)
      {
        Debug.debugException(e);
        throw new LDAPException(ResultCode.LOCAL_ERROR,
             ERR_SCHEMA_CANNOT_LOAD_DEFAULT_DEFINITIONS.get(
                  StaticUtils.getExceptionMessage(e)),
             e);
      }
    }
  }



  /**
   * Retrieves a schema containing all of the elements of each of the provided
   * schemas.
   *
   * @param  schemas  The schemas to be merged.  It must not be {@code null} or
   *                  empty.
   *
   * @return  A merged representation of the provided schemas.
   */
  @Nullable()
  public static Schema mergeSchemas(@NotNull final Schema... schemas)
  {
    if ((schemas == null) || (schemas.length == 0))
    {
      return null;
    }
    else if (schemas.length == 1)
    {
      return schemas[0];
    }

    final LinkedHashMap asMap =
         new LinkedHashMap<>(StaticUtils.computeMapCapacity(100));
    final LinkedHashMap atMap =
         new LinkedHashMap<>(StaticUtils.computeMapCapacity(100));
    final LinkedHashMap dcrMap =
         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
    final LinkedHashMap dsrMap =
         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
    final LinkedHashMap mrMap =
         new LinkedHashMap<>(StaticUtils.computeMapCapacity(100));
    final LinkedHashMap mruMap =
         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
    final LinkedHashMap nfMap =
         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
    final LinkedHashMap ocMap =
         new LinkedHashMap<>(StaticUtils.computeMapCapacity(100));

    for (final Schema s : schemas)
    {
      for (final AttributeSyntaxDefinition as : s.asSet)
      {
        asMap.put(StaticUtils.toLowerCase(as.getOID()), as.toString());
      }

      for (final AttributeTypeDefinition at : s.atSet)
      {
        atMap.put(StaticUtils.toLowerCase(at.getOID()), at.toString());
      }

      for (final DITContentRuleDefinition dcr : s.dcrSet)
      {
        dcrMap.put(StaticUtils.toLowerCase(dcr.getOID()), dcr.toString());
      }

      for (final DITStructureRuleDefinition dsr : s.dsrSet)
      {
        dsrMap.put(dsr.getRuleID(), dsr.toString());
      }

      for (final MatchingRuleDefinition mr : s.mrSet)
      {
        mrMap.put(StaticUtils.toLowerCase(mr.getOID()), mr.toString());
      }

      for (final MatchingRuleUseDefinition mru : s.mruSet)
      {
        mruMap.put(StaticUtils.toLowerCase(mru.getOID()), mru.toString());
      }

      for (final NameFormDefinition nf : s.nfSet)
      {
        nfMap.put(StaticUtils.toLowerCase(nf.getOID()), nf.toString());
      }

      for (final ObjectClassDefinition oc : s.ocSet)
      {
        ocMap.put(StaticUtils.toLowerCase(oc.getOID()), oc.toString());
      }
    }

    final Entry e = new Entry(schemas[0].getSchemaEntry().getDN());

    final Attribute ocAttr =
         schemas[0].getSchemaEntry().getObjectClassAttribute();
    if (ocAttr == null)
    {
      e.addAttribute("objectClass", "top", "ldapSubEntry", "subschema");
    }
    else
    {
      e.addAttribute(ocAttr);
    }

    if (! asMap.isEmpty())
    {
      final String[] values = new String[asMap.size()];
      e.addAttribute(ATTR_ATTRIBUTE_SYNTAX, asMap.values().toArray(values));
    }

    if (! mrMap.isEmpty())
    {
      final String[] values = new String[mrMap.size()];
      e.addAttribute(ATTR_MATCHING_RULE, mrMap.values().toArray(values));
    }

    if (! atMap.isEmpty())
    {
      final String[] values = new String[atMap.size()];
      e.addAttribute(ATTR_ATTRIBUTE_TYPE, atMap.values().toArray(values));
    }

    if (! ocMap.isEmpty())
    {
      final String[] values = new String[ocMap.size()];
      e.addAttribute(ATTR_OBJECT_CLASS, ocMap.values().toArray(values));
    }

    if (! dcrMap.isEmpty())
    {
      final String[] values = new String[dcrMap.size()];
      e.addAttribute(ATTR_DIT_CONTENT_RULE, dcrMap.values().toArray(values));
    }

    if (! dsrMap.isEmpty())
    {
      final String[] values = new String[dsrMap.size()];
      e.addAttribute(ATTR_DIT_STRUCTURE_RULE, dsrMap.values().toArray(values));
    }

    if (! nfMap.isEmpty())
    {
      final String[] values = new String[nfMap.size()];
      e.addAttribute(ATTR_NAME_FORM, nfMap.values().toArray(values));
    }

    if (! mruMap.isEmpty())
    {
      final String[] values = new String[mruMap.size()];
      e.addAttribute(ATTR_MATCHING_RULE_USE, mruMap.values().toArray(values));
    }

    return new Schema(e);
  }



  /**
   * Retrieves the entry used to create this schema object.
   *
   * @return  The entry used to create this schema object.
   */
  @NotNull()
  public ReadOnlyEntry getSchemaEntry()
  {
    return schemaEntry;
  }



  /**
   * Retrieves the value of the subschemaSubentry attribute from the specified
   * entry using the provided connection.
   *
   * @param  connection  The connection to use in order to perform the search.
   *                     It must not be {@code null}.
   * @param  entryDN     The DN of the entry from which to retrieve the
   *                     subschemaSubentry attribute.  It may be {@code null} or
   *                     an empty string in order to retrieve the value from the
   *                     server's root DSE.
   *
   * @return  The value of the subschemaSubentry attribute from the specified
   *          entry, or {@code null} if it is not available for some reason
   *          (e.g., the client does not have permission to read the target
   *          entry or the subschemaSubentry attribute).
   *
   * @throws  LDAPException  If a problem occurs while attempting to retrieve
   *                         the specified entry.
   */
  @Nullable()
  public static String getSubschemaSubentryDN(
                            @NotNull final LDAPConnection connection,
                            @Nullable final String entryDN)
         throws LDAPException
  {
    Validator.ensureNotNull(connection);

    final Entry e;
    if (entryDN == null)
    {
      e = connection.getEntry("", SUBSCHEMA_SUBENTRY_REQUEST_ATTRS);
    }
    else
    {
      e = connection.getEntry(entryDN, SUBSCHEMA_SUBENTRY_REQUEST_ATTRS);
    }

    if (e == null)
    {
      return null;
    }

    return e.getAttributeValue(ATTR_SUBSCHEMA_SUBENTRY);
  }



  /**
   * Retrieves the set of attribute syntax definitions contained in the server
   * schema.
   *
   * @return  The set of attribute syntax definitions contained in the server
   *          schema.
   */
  @NotNull()
  public Set getAttributeSyntaxes()
  {
    return asSet;
  }



  /**
   * Retrieves the attribute syntax with the specified OID from the server
   * schema.
   *
   * @param  oid  The OID of the attribute syntax to retrieve.  It must not be
   *              {@code null}.  It may optionally include a minimum upper bound
   *              (as may appear when the syntax OID is included in an attribute
   *              type definition), but if it does then that portion will be
   *              ignored when retrieving the attribute syntax.
   *
   * @return  The requested attribute syntax, or {@code null} if there is no
   *          such syntax defined in the server schema.
   */
  @Nullable()
  public AttributeSyntaxDefinition getAttributeSyntax(@NotNull final String oid)
  {
    Validator.ensureNotNull(oid);

    final String lowerOID = StaticUtils.toLowerCase(oid);
    final int    curlyPos = lowerOID.indexOf('{');

    if (curlyPos > 0)
    {
      return asMap.get(lowerOID.substring(0, curlyPos));
    }
    else
    {
      return asMap.get(lowerOID);
    }
  }



  /**
   * Retrieves the set of attribute type definitions contained in the server
   * schema.
   *
   * @return  The set of attribute type definitions contained in the server
   *          schema.
   */
  @NotNull()
  public Set getAttributeTypes()
  {
    return atSet;
  }



  /**
   * Retrieves the set of operational attribute type definitions (i.e., those
   * definitions with a usage of directoryOperation, distributedOperation, or
   * dSAOperation) contained in the  server  schema.
   *
   * @return  The set of operational attribute type definitions contained in the
   *          server schema.
   */
  @NotNull()
  public Set getOperationalAttributeTypes()
  {
    return operationalATSet;
  }



  /**
   * Retrieves the set of user attribute type definitions (i.e., those
   * definitions with a usage of userApplications) contained in the  server
   * schema.
   *
   * @return  The set of user attribute type definitions contained in the server
   *          schema.
   */
  @NotNull()
  public Set getUserAttributeTypes()
  {
    return userATSet;
  }



  /**
   * Retrieves the attribute type with the specified name or OID from the server
   * schema.
   *
   * @param  name  The name or OID of the attribute type to retrieve.  It must
   *               not be {@code null}.
   *
   * @return  The requested attribute type, or {@code null} if there is no
   *          such attribute type defined in the server schema.
   */
  @Nullable()
  public AttributeTypeDefinition getAttributeType(@NotNull final String name)
  {
    Validator.ensureNotNull(name);

    return atMap.get(StaticUtils.toLowerCase(name));
  }



  /**
   * Retrieves a list of all subordinate attribute type definitions for the
   * provided attribute type definition.
   *
   * @param  d  The attribute type definition for which to retrieve all
   *            subordinate attribute types.  It must not be {@code null}.
   *
   * @return  A list of all subordinate attribute type definitions for the
   *          provided attribute type definition, or an empty list if it does
   *          not have any subordinate types or the provided attribute type is
   *          not defined in the schema.
   */
  @NotNull()
  public List getSubordinateAttributeTypes(
              @NotNull final AttributeTypeDefinition d)
  {
    Validator.ensureNotNull(d);

    final List l = subordinateAttributeTypes.get(d);
    if (l == null)
    {
      return Collections.emptyList();
    }
    else
    {
      return Collections.unmodifiableList(l);
    }
  }



  /**
   * Retrieves the set of DIT content rule definitions contained in the server
   * schema.
   *
   * @return  The set of DIT content rule definitions contained in the server
   *          schema.
   */
  @NotNull()
  public Set getDITContentRules()
  {
    return dcrSet;
  }



  /**
   * Retrieves the DIT content rule with the specified name or OID from the
   * server schema.
   *
   * @param  name  The name or OID of the DIT content rule to retrieve.  It must
   *               not be {@code null}.
   *
   * @return  The requested DIT content rule, or {@code null} if there is no
   *          such rule defined in the server schema.
   */
  @Nullable()
  public DITContentRuleDefinition getDITContentRule(@NotNull final String name)
  {
    Validator.ensureNotNull(name);

    return dcrMap.get(StaticUtils.toLowerCase(name));
  }



  /**
   * Retrieves the set of DIT structure rule definitions contained in the server
   * schema.
   *
   * @return  The set of DIT structure rule definitions contained in the server
   *          schema.
   */
  @NotNull()
  public Set getDITStructureRules()
  {
    return dsrSet;
  }



  /**
   * Retrieves the DIT content rule with the specified rule ID from the server
   * schema.
   *
   * @param  ruleID  The rule ID for the DIT structure rule to retrieve.
   *
   * @return  The requested DIT structure rule, or {@code null} if there is no
   *          such rule defined in the server schema.
   */
  @Nullable()
  public DITStructureRuleDefinition getDITStructureRuleByID(final int ruleID)
  {
    return dsrMapByID.get(ruleID);
  }



  /**
   * Retrieves the DIT content rule with the specified name from the server
   * schema.
   *
   * @param  ruleName  The name of the DIT structure rule to retrieve.  It must
   *                   not be {@code null}.
   *
   * @return  The requested DIT structure rule, or {@code null} if there is no
   *          such rule defined in the server schema.
   */
  @Nullable()
  public DITStructureRuleDefinition getDITStructureRuleByName(
                                         @NotNull final String ruleName)
  {
    Validator.ensureNotNull(ruleName);

    return dsrMapByName.get(StaticUtils.toLowerCase(ruleName));
  }



  /**
   * Retrieves the DIT content rule associated with the specified name form from
   * the server schema.
   *
   * @param  nameForm  The name or OID of the name form for which to retrieve
   *                   the associated DIT structure rule.
   *
   * @return  The requested DIT structure rule, or {@code null} if there is no
   *          such rule defined in the server schema.
   */
  @Nullable()
  public DITStructureRuleDefinition getDITStructureRuleByNameForm(
                                         @NotNull final String nameForm)
  {
    Validator.ensureNotNull(nameForm);

    return dsrMapByNameForm.get(StaticUtils.toLowerCase(nameForm));
  }



  /**
   * Retrieves the set of matching rule definitions contained in the server
   * schema.
   *
   * @return  The set of matching rule definitions contained in the server
   *          schema.
   */
  @NotNull()
  public Set getMatchingRules()
  {
    return mrSet;
  }



  /**
   * Retrieves the matching rule with the specified name or OID from the server
   * schema.
   *
   * @param  name  The name or OID of the matching rule to retrieve.  It must
   *               not be {@code null}.
   *
   * @return  The requested matching rule, or {@code null} if there is no
   *          such rule defined in the server schema.
   */
  @Nullable()
  public MatchingRuleDefinition getMatchingRule(@NotNull final String name)
  {
    Validator.ensureNotNull(name);

    return mrMap.get(StaticUtils.toLowerCase(name));
  }



  /**
   * Retrieves the set of matching rule use definitions contained in the server
   * schema.
   *
   * @return  The set of matching rule use definitions contained in the server
   *          schema.
   */
  @NotNull()
  public Set getMatchingRuleUses()
  {
    return mruSet;
  }



  /**
   * Retrieves the matching rule use with the specified name or OID from the
   * server schema.
   *
   * @param  name  The name or OID of the matching rule use to retrieve.  It
   *               must not be {@code null}.
   *
   * @return  The requested matching rule, or {@code null} if there is no
   *          such matching rule use defined in the server schema.
   */
  @Nullable()
  public MatchingRuleUseDefinition getMatchingRuleUse(
              @NotNull final String name)
  {
    Validator.ensureNotNull(name);

    return mruMap.get(StaticUtils.toLowerCase(name));
  }



  /**
   * Retrieves the set of name form definitions contained in the server schema.
   *
   * @return  The set of name form definitions contained in the server schema.
   */
  @NotNull()
  public Set getNameForms()
  {
    return nfSet;
  }



  /**
   * Retrieves the name form with the specified name or OID from the server
   * schema.
   *
   * @param  name  The name or OID of the name form to retrieve.  It must not be
   *               {@code null}.
   *
   * @return  The requested name form, or {@code null} if there is no
   *          such rule defined in the server schema.
   */
  @Nullable()
  public NameFormDefinition getNameFormByName(@NotNull final String name)
  {
    Validator.ensureNotNull(name);

    return nfMapByName.get(StaticUtils.toLowerCase(name));
  }



  /**
   * Retrieves the name form associated with the specified structural object
   * class from the server schema.
   *
   * @param  objectClass  The name or OID of the structural object class for
   *                      which to retrieve the associated name form.  It must
   *                      not be {@code null}.
   *
   * @return  The requested name form, or {@code null} if there is no
   *          such rule defined in the server schema.
   */
  @NotNull()
  public NameFormDefinition getNameFormByObjectClass(
                                 @NotNull final String objectClass)
  {
    Validator.ensureNotNull(objectClass);

    return nfMapByOC.get(StaticUtils.toLowerCase(objectClass));
  }



  /**
   * Retrieves the set of object class definitions contained in the server
   * schema.
   *
   * @return  The set of object class definitions contained in the server
   *          schema.
   */
  @NotNull()
  public Set getObjectClasses()
  {
    return ocSet;
  }



  /**
   * Retrieves the set of abstract object class definitions contained in the
   * server schema.
   *
   * @return  The set of abstract object class definitions contained in the
   *          server schema.
   */
  @NotNull()
  public Set getAbstractObjectClasses()
  {
    return abstractOCSet;
  }



  /**
   * Retrieves the set of auxiliary object class definitions contained in the
   * server schema.
   *
   * @return  The set of auxiliary object class definitions contained in the
   *          server schema.
   */
  @NotNull()
  public Set getAuxiliaryObjectClasses()
  {
    return auxiliaryOCSet;
  }



  /**
   * Retrieves the set of structural object class definitions contained in the
   * server schema.
   *
   * @return  The set of structural object class definitions contained in the
   *          server schema.
   */
  @NotNull()
  public Set getStructuralObjectClasses()
  {
    return structuralOCSet;
  }



  /**
   * Retrieves the object class with the specified name or OID from the server
   * schema.
   *
   * @param  name  The name or OID of the object class to retrieve.  It must
   *               not be {@code null}.
   *
   * @return  The requested object class, or {@code null} if there is no such
   *          class defined in the server schema.
   */
  @Nullable()
  public ObjectClassDefinition getObjectClass(@NotNull final String name)
  {
    Validator.ensureNotNull(name);

    return ocMap.get(StaticUtils.toLowerCase(name));
  }



  /**
   * Retrieves a hash code for this schema object.
   *
   * @return  A hash code for this schema object.
   */
  @Override()
  public int hashCode()
  {
    int hc;
    try
    {
      hc = schemaEntry.getParsedDN().hashCode();
    }
    catch (final Exception e)
    {
      Debug.debugException(e);
      hc = StaticUtils.toLowerCase(schemaEntry.getDN()).hashCode();
    }

    Attribute a = schemaEntry.getAttribute(ATTR_ATTRIBUTE_SYNTAX);
    if (a != null)
    {
      hc += a.hashCode();
    }

    a = schemaEntry.getAttribute(ATTR_MATCHING_RULE);
    if (a != null)
    {
      hc += a.hashCode();
    }

    a = schemaEntry.getAttribute(ATTR_ATTRIBUTE_TYPE);
    if (a != null)
    {
      hc += a.hashCode();
    }

    a = schemaEntry.getAttribute(ATTR_OBJECT_CLASS);
    if (a != null)
    {
      hc += a.hashCode();
    }

    a = schemaEntry.getAttribute(ATTR_NAME_FORM);
    if (a != null)
    {
      hc += a.hashCode();
    }

    a = schemaEntry.getAttribute(ATTR_DIT_CONTENT_RULE);
    if (a != null)
    {
      hc += a.hashCode();
    }

    a = schemaEntry.getAttribute(ATTR_DIT_STRUCTURE_RULE);
    if (a != null)
    {
      hc += a.hashCode();
    }

    a = schemaEntry.getAttribute(ATTR_MATCHING_RULE_USE);
    if (a != null)
    {
      hc += a.hashCode();
    }

    return hc;
  }



  /**
   * Indicates whether the provided object is equal to this schema object.
   *
   * @param  o  The object for which to make the determination.
   *
   * @return  {@code true} if the provided object is equal to this schema
   *          object, or {@code false} if not.
   */
  @Override()
  public boolean equals(@Nullable final Object o)
  {
    if (o == null)
    {
      return false;
    }

    if (o == this)
    {
      return true;
    }

    if (! (o instanceof Schema))
    {
      return false;
    }

    final Schema s = (Schema) o;

    try
    {
      if (! schemaEntry.getParsedDN().equals(s.schemaEntry.getParsedDN()))
      {
        return false;
      }
    }
    catch (final Exception e)
    {
      Debug.debugException(e);
      if (! schemaEntry.getDN().equalsIgnoreCase(s.schemaEntry.getDN()))
      {
        return false;
      }
    }

    return (asSet.equals(s.asSet) &&
         mrSet.equals(s.mrSet) &&
         atSet.equals(s.atSet) &&
         ocSet.equals(s.ocSet) &&
         nfSet.equals(s.nfSet) &&
         dcrSet.equals(s.dcrSet) &&
         dsrSet.equals(s.dsrSet) &&
         mruSet.equals(s.mruSet));
  }



  /**
   * Retrieves a string representation of the associated schema entry.
   *
   * @return  A string representation of the associated schema entry.
   */
  @Override()
  @NotNull()
  public String toString()
  {
    return schemaEntry.toString();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy