/*
 *   Licensed to the Apache Software Foundation (ASF) under one
 *   or more contributor license agreements.  See the NOTICE file
 *   distributed with this work for additional information
 *   regarding copyright ownership.  The ASF licenses this file
 *   to you 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
 *
 *     https://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.
 *
 */

package org.apache.directory.ldap.client.api;


import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Set;

import org.apache.directory.api.i18n.I18n;
import org.apache.directory.api.ldap.model.constants.MetaSchemaConstants;
import org.apache.directory.api.ldap.model.constants.SchemaConstants;
import org.apache.directory.api.ldap.model.cursor.CursorException;
import org.apache.directory.api.ldap.model.cursor.EntryCursor;
import org.apache.directory.api.ldap.model.entry.Attribute;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.entry.Value;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.schema.AttributeType;
import org.apache.directory.api.ldap.model.schema.AttributesFactory;
import org.apache.directory.api.ldap.model.schema.DitContentRule;
import org.apache.directory.api.ldap.model.schema.DitStructureRule;
import org.apache.directory.api.ldap.model.schema.LdapSyntax;
import org.apache.directory.api.ldap.model.schema.MatchingRule;
import org.apache.directory.api.ldap.model.schema.MatchingRuleUse;
import org.apache.directory.api.ldap.model.schema.NameForm;
import org.apache.directory.api.ldap.model.schema.ObjectClass;
import org.apache.directory.api.ldap.model.schema.SchemaObject;
import org.apache.directory.api.ldap.model.schema.SchemaObjectWrapper;
import org.apache.directory.api.ldap.model.schema.parsers.AttributeTypeDescriptionSchemaParser;
import org.apache.directory.api.ldap.model.schema.parsers.DitContentRuleDescriptionSchemaParser;
import org.apache.directory.api.ldap.model.schema.parsers.DitStructureRuleDescriptionSchemaParser;
import org.apache.directory.api.ldap.model.schema.parsers.LdapComparatorDescription;
import org.apache.directory.api.ldap.model.schema.parsers.LdapComparatorDescriptionSchemaParser;
import org.apache.directory.api.ldap.model.schema.parsers.LdapSyntaxDescriptionSchemaParser;
import org.apache.directory.api.ldap.model.schema.parsers.MatchingRuleDescriptionSchemaParser;
import org.apache.directory.api.ldap.model.schema.parsers.MatchingRuleUseDescriptionSchemaParser;
import org.apache.directory.api.ldap.model.schema.parsers.NameFormDescriptionSchemaParser;
import org.apache.directory.api.ldap.model.schema.parsers.NormalizerDescription;
import org.apache.directory.api.ldap.model.schema.parsers.NormalizerDescriptionSchemaParser;
import org.apache.directory.api.ldap.model.schema.parsers.ObjectClassDescriptionSchemaParser;
import org.apache.directory.api.ldap.model.schema.parsers.SyntaxCheckerDescription;
import org.apache.directory.api.ldap.model.schema.parsers.SyntaxCheckerDescriptionSchemaParser;
import org.apache.directory.api.ldap.model.schema.registries.AbstractSchemaLoader;
import org.apache.directory.api.ldap.model.schema.registries.DefaultSchema;
import org.apache.directory.api.ldap.model.schema.registries.Schema;
import org.apache.directory.api.util.Strings;
import org.apache.directory.ldap.client.api.exception.InvalidConnectionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * A schema loader which uses LdapConnection to load schema from a ApacheDS serveur
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class DefaultSchemaLoader extends AbstractSchemaLoader
{
    private static final String DEFAULT_APACHEDS_VENDOR_NAME = "Apache Software Foundation";

    /** the logger */
    private static final Logger LOG = LoggerFactory.getLogger( DefaultSchemaLoader.class );

    /** the connection to the ldap server */
    private LdapConnection connection;

    /** the subschemaSubentry DN */
    private Dn subschemaSubentryDn;

    /** The SubschemaSubentry descriptions parsers */
    private static final AttributeTypeDescriptionSchemaParser AT_DESCR_SCHEMA_PARSER = new AttributeTypeDescriptionSchemaParser();
    private static final DitStructureRuleDescriptionSchemaParser DSR_DESCR_SCHEMA_PARSER = new DitStructureRuleDescriptionSchemaParser();
    private static final DitContentRuleDescriptionSchemaParser DCR_DESCR_SCHEMA_PARSER = new DitContentRuleDescriptionSchemaParser();
    private static final MatchingRuleDescriptionSchemaParser MR_DESCR_SCHEMA_PARSER = new MatchingRuleDescriptionSchemaParser();
    private static final MatchingRuleUseDescriptionSchemaParser MRU_DESCR_SCHEMA_PARSER = new MatchingRuleUseDescriptionSchemaParser();
    private static final NameFormDescriptionSchemaParser NF_DESCR_SCHEMA_PARSER = new NameFormDescriptionSchemaParser();
    private static final ObjectClassDescriptionSchemaParser OC_DESCR_SCHEMA_PARSER = new ObjectClassDescriptionSchemaParser();
    private static final LdapSyntaxDescriptionSchemaParser LS_DESCR_SCHEMA_PARSER = new LdapSyntaxDescriptionSchemaParser();

    private static final LdapComparatorDescriptionSchemaParser C_DESCR_SCHEMA_PARSER = new LdapComparatorDescriptionSchemaParser();
    private static final NormalizerDescriptionSchemaParser N_DESCR_SCHEMA_PARSER = new NormalizerDescriptionSchemaParser();
    private static final SyntaxCheckerDescriptionSchemaParser SC_DESCR_SCHEMA_PARSER = new SyntaxCheckerDescriptionSchemaParser();

    /**
     * A default constructor
     * 
     * @throws LdapException If the instance creation failed
     */
    protected DefaultSchemaLoader() throws LdapException
    {
        // For use in subclasses
    }

    /**
     * Creates a new instance of DefaultSchemaLoader.
     *
     * @param connection the LDAP connection
     * @throws LdapException if the connection is not authenticated or if there are any problems
     *                   while loading the schema entries
     */
    public DefaultSchemaLoader( LdapConnection connection ) throws LdapException
    {
        this( connection, false );
    }


    /**
     * Creates a new instance of DefaultSchemaLoader.
     *
     * @param connection the LDAP connection
     * @param relaxed initial setting for the relaxed mode
     * @throws LdapException if the connection is not authenticated or if there are any problems
     *                   while loading the schema entries
     */
    public DefaultSchemaLoader( LdapConnection connection, boolean relaxed ) throws LdapException
    {
        if ( connection == null )
        {
            throw new InvalidConnectionException( I18n.err( I18n.ERR_04104_NULL_CONNECTION_CANNOT_CONNECT ) );
        }

        this.connection = connection;
        setRelaxed( relaxed );
        setQuirksMode( relaxed );

        // Flagging if the connection was already connected
        boolean wasConnected = connection.isConnected();

        try
        {
            // Connecting (if needed)
            if ( !wasConnected )
            {
                connection.connect();
            }

            // Getting the subschemaSubentry DN from the rootDSE
            Entry rootDse = connection.lookup( Dn.ROOT_DSE, SchemaConstants.SUBSCHEMA_SUBENTRY_AT,
                SchemaConstants.VENDOR_NAME_AT );

            if ( rootDse != null )
            {
                // Checking if this is an ApacheDS server
                if ( isApacheDs( rootDse ) )
                {
                    // Getting the subSchemaSubEntry attribute
                    Attribute subschemaSubentryAttribute = rootDse.get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT );

                    if ( ( subschemaSubentryAttribute != null ) && ( subschemaSubentryAttribute.size() > 0 ) )
                    {
                        subschemaSubentryDn = new Dn( connection.getSchemaManager(),
                            subschemaSubentryAttribute.getString() );

                        loadSchemas();
                    }
                }
                else
                {
                    try
                    {
                        // No matter what, first try to search the schema from the rootDSE
                        // Getting the subSchemaSubEntry attribute
                        Attribute subschemaSubentryAttribute = rootDse.get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT );

                        if ( ( subschemaSubentryAttribute != null ) && ( subschemaSubentryAttribute.size() > 0 ) )
                        {
                            subschemaSubentryDn = new Dn( connection.getSchemaManager(),
                                subschemaSubentryAttribute.getString() );

                            loadSchemas();
                        }
                    }
                    catch ( LdapException le )
                    {
                        // TODO : if we can't read the schema from the rootDSE, just try to read the 
                        // schema from cn=schema
                        throw le;
                    }
                }
            }
        }
        finally
        {
            // Checking if the connection needs to be closed
            if ( ( !wasConnected ) && ( connection.isConnected() ) )
            {
                try
                {
                    connection.close();
                }
                catch ( IOException e )
                {
                    throw new LdapException( e );
                }
            }
        }
    }

    /**
     * Creates a new instance of NetworkSchemaLoader.
     *
     * @param connection the LDAP connection
     * @param subschemaSubentryDn The SubschemaSubentry
     * @throws LdapException if the connection is not authenticated or if there are any problems
     *                   while loading the schema entries
     */
    public DefaultSchemaLoader( LdapConnection connection, Dn subschemaSubentryDn ) throws LdapException
    {
        if ( !connection.isAuthenticated() )
        {
            throw new IllegalArgumentException( I18n.err( I18n.ERR_04105_CONNECTION_NOT_AUTHENTICATED ) );
        }

        this.connection = connection;
        this.subschemaSubentryDn = subschemaSubentryDn;

        loadSchemas();
    }

    /**
     * Get the LdapConnecgion instance
     * 
     * @return the LdapConnecgion instance
     */
    protected LdapConnection getConnection()
    {
        return connection;
    }

    /**
     * Set the LdapConnection to use
     * 
     * @param connection The LdapConnection to use
     */
    protected void setConnection( LdapConnection connection )
    {
        this.connection = connection;
    }

    /**
     * Indicates if the given Root DSE corresponds to an ApacheDS server.
     *
     * @param rootDse the Root DSE
     * @return <code>true</code> if this is an ApacheDS server,
     *         <code>false</code> if not.
     * @throws LdapInvalidAttributeValueException If the vendorName attribute contains an invalid value 
     */
    private boolean isApacheDs( Entry rootDse ) throws LdapInvalidAttributeValueException
    {
        if ( rootDse != null )
        {
            Attribute vendorNameAttribute = rootDse.get( SchemaConstants.VENDOR_NAME_AT );

            if ( ( vendorNameAttribute != null ) && vendorNameAttribute.size() == 1 )
            {
                return DEFAULT_APACHEDS_VENDOR_NAME.equalsIgnoreCase( vendorNameAttribute.getString() );
            }
        }

        return false;
    }


    /**
     * Load all the schemas.
     * 
     * @throws LdapException If one schema can't be loaded
     */
    private void loadSchemas() throws LdapException
    {
        if ( LOG.isDebugEnabled() )
        {
            LOG.debug( I18n.msg( I18n.MSG_04160_INITIALIZING_SCHEMAS ) );
        }

        // Load all the elements from the SubschemaSubentry
        EntryCursor subschemaSubentryCursor = connection.search( subschemaSubentryDn,
            "(objectClass=subschema)",
            SearchScope.OBJECT,
            SchemaConstants.ATTRIBUTE_TYPES_AT,
            SchemaConstants.COMPARATORS_AT,
            SchemaConstants.DIT_CONTENT_RULES_AT,
            SchemaConstants.DIT_STRUCTURE_RULES_AT,
            SchemaConstants.LDAP_SYNTAXES_AT,
            SchemaConstants.MATCHING_RULES_AT,
            SchemaConstants.MATCHING_RULE_USE_AT,
            SchemaConstants.NAME_FORMS_AT,
            SchemaConstants.NORMALIZERS_AT,
            SchemaConstants.OBJECT_CLASSES_AT,
            SchemaConstants.SYNTAX_CHECKERS_AT
            );

        try 
        {
            if ( !subschemaSubentryCursor.next() )
            {
                throw new LdapException( "Unable to read schema at: " + subschemaSubentryDn );
            }
            Entry subschemaSubentry = subschemaSubentryCursor.get();

            // Load all the AT
            Attribute attributeTypes = subschemaSubentry.get( SchemaConstants.ATTRIBUTE_TYPES_AT );
            loadAttributeTypes( attributeTypes );

            // Load all the C
            Attribute comparators = subschemaSubentry.get( SchemaConstants.COMPARATORS_AT );
            loadComparators( comparators );

            // Load all the DCR
            Attribute ditContentRules = subschemaSubentry.get( SchemaConstants.DIT_CONTENT_RULES_AT );
            loadDitContentRules( ditContentRules );

            // Load all the DSR
            Attribute ditStructureRules = subschemaSubentry.get( SchemaConstants.DIT_STRUCTURE_RULES_AT );
            loadDitStructureRules( ditStructureRules );

            // Load all the LS
            Attribute ldapSytaxes = subschemaSubentry.get( SchemaConstants.LDAP_SYNTAXES_AT );
            loadLdapSyntaxes( ldapSytaxes );

            // Load all the MR
            Attribute matchingRules = subschemaSubentry.get( SchemaConstants.MATCHING_RULES_AT );
            loadMatchingRules( matchingRules );

            // Load all the MRU
            Attribute matchingRuleUse = subschemaSubentry.get( SchemaConstants.MATCHING_RULE_USE_AT );
            loadMatchingRuleUses( matchingRuleUse );

            // Load all the N
            Attribute normalizers = subschemaSubentry.get( SchemaConstants.NORMALIZERS_AT );
            loadNormalizers( normalizers );

            // Load all the NF
            Attribute nameForms = subschemaSubentry.get( SchemaConstants.NAME_FORMS_AT );
            loadNameForms( nameForms );

            // Load all the OC
            Attribute objectClasses = subschemaSubentry.get( SchemaConstants.OBJECT_CLASSES_AT );
            loadObjectClasses( objectClasses );

            // Load all the SC
            Attribute syntaxCheckers = subschemaSubentry.get( SchemaConstants.SYNTAX_CHECKERS_AT );
            loadSyntaxCheckers( syntaxCheckers );
        }
        catch ( CursorException e )
        {
            throw new LdapException( e.getMessage(), e );
        }
       
    }


    private void loadAttributeTypes( Attribute attributeTypes ) throws LdapException
    {
        if ( attributeTypes == null )
        {
            return;
        }

        for ( Value value : attributeTypes )
        {
            String desc = value.getString();

            try
            {
                AttributeType attributeType = AT_DESCR_SCHEMA_PARSER.parse( desc );

                updateSchemas( attributeType );
            }
            catch ( ParseException pe )
            {
                throw new LdapException( pe );
            }
        }
    }


    private void loadComparators( Attribute comparators ) throws LdapException
    {
        if ( comparators == null )
        {
            return;
        }

        for ( Value value : comparators )
        {
            String desc = value.getString();

            try
            {
                LdapComparatorDescription comparator = C_DESCR_SCHEMA_PARSER.parse( desc );

                updateSchemas( comparator );
            }
            catch ( ParseException pe )
            {
                throw new LdapException( pe );
            }
        }
    }


    private void loadDitContentRules( Attribute ditContentRules ) throws LdapException
    {
        if ( ditContentRules == null )
        {
            return;
        }

        for ( Value value : ditContentRules )
        {
            String desc = value.getString();

            try
            {
                DitContentRule ditContentRule = DCR_DESCR_SCHEMA_PARSER.parse( desc );

                updateSchemas( ditContentRule );
            }
            catch ( ParseException pe )
            {
                throw new LdapException( pe );
            }
        }
    }


    private void loadDitStructureRules( Attribute ditStructureRules ) throws LdapException
    {
        if ( ditStructureRules == null )
        {
            return;
        }

        for ( Value value : ditStructureRules )
        {
            String desc = value.getString();

            try
            {
                DitStructureRule ditStructureRule = DSR_DESCR_SCHEMA_PARSER.parse( desc );

                updateSchemas( ditStructureRule );
            }
            catch ( ParseException pe )
            {
                throw new LdapException( pe );
            }
        }
    }


    private void loadLdapSyntaxes( Attribute ldapSyntaxes ) throws LdapException
    {
        if ( ldapSyntaxes == null )
        {
            return;
        }

        for ( Value value : ldapSyntaxes )
        {
            String desc = value.getString();

            try
            {
                LdapSyntax ldapSyntax = LS_DESCR_SCHEMA_PARSER.parse( desc );

                updateSchemas( ldapSyntax );
            }
            catch ( ParseException pe )
            {
                throw new LdapException( pe );
            }
        }
    }


    private void loadMatchingRules( Attribute matchingRules ) throws LdapException
    {
        if ( matchingRules == null )
        {
            return;
        }

        for ( Value value : matchingRules )
        {
            String desc = value.getString();

            try
            {
                MatchingRule matchingRule = MR_DESCR_SCHEMA_PARSER.parse( desc );

                updateSchemas( matchingRule );
            }
            catch ( ParseException pe )
            {
                throw new LdapException( pe );
            }
        }
    }


    private void loadMatchingRuleUses( Attribute matchingRuleUses ) throws LdapException
    {
        if ( matchingRuleUses == null )
        {
            return;
        }

        for ( Value value : matchingRuleUses )
        {
            String desc = value.getString();

            try
            {
                MatchingRuleUse matchingRuleUse = MRU_DESCR_SCHEMA_PARSER.parse( desc );

                updateSchemas( matchingRuleUse );
            }
            catch ( ParseException pe )
            {
                throw new LdapException( pe );
            }
        }
    }


    private void loadNameForms( Attribute nameForms ) throws LdapException
    {
        if ( nameForms == null )
        {
            return;
        }

        for ( Value value : nameForms )
        {
            String desc = value.getString();

            try
            {
                NameForm nameForm = NF_DESCR_SCHEMA_PARSER.parse( desc );

                updateSchemas( nameForm );
            }
            catch ( ParseException pe )
            {
                throw new LdapException( pe );
            }
        }
    }


    private void loadNormalizers( Attribute normalizers ) throws LdapException
    {
        if ( normalizers == null )
        {
            return;
        }

        for ( Value value : normalizers )
        {
            String desc = value.getString();

            try
            {
                NormalizerDescription normalizer = N_DESCR_SCHEMA_PARSER.parse( desc );

                updateSchemas( normalizer );
            }
            catch ( ParseException pe )
            {
                throw new LdapException( pe );
            }
        }
    }


    private void loadObjectClasses( Attribute objectClasses ) throws LdapException
    {
        if ( objectClasses == null )
        {
            return;
        }

        for ( Value value : objectClasses )
        {
            String desc = value.getString();

            try
            {
                ObjectClass objectClass = OC_DESCR_SCHEMA_PARSER.parse( desc );

                updateSchemas( objectClass );
            }
            catch ( ParseException pe )
            {
                throw new LdapException( pe );
            }
        }
    }


    private void loadSyntaxCheckers( Attribute syntaxCheckers ) throws LdapException
    {
        if ( syntaxCheckers == null )
        {
            return;
        }

        for ( Value value : syntaxCheckers )
        {
            String desc = value.getString();

            try
            {
                SyntaxCheckerDescription syntaxChecker = SC_DESCR_SCHEMA_PARSER.parse( desc );

                updateSchemas( syntaxChecker );
            }
            catch ( ParseException pe )
            {
                throw new LdapException( pe );
            }
        }
    }


    /**
     * Update the schema with the provided schema object
     * 
     * @param schemaObject The schema object to add to the current schema
     */
    protected void updateSchemas( SchemaObject schemaObject )
    {
        String schemaName = schemaObject.getSchemaName();
        Schema schema;

        if ( Strings.isEmpty( schemaName ) || "null".equals( schemaName ) )
        {
            schemaName = "default";
            schema = schemaMap.get( schemaName );
        }
        else
        {
            schema = schemaMap.get( schemaName );
        }

        if ( schema == null )
        {
            schema = new DefaultSchema( this, schemaName );

            schemaMap.put( schemaName, schema );
        }

        schema.getContent().add( new SchemaObjectWrapper( schemaObject ) );

    }


    /**
     * {@inheritDoc}
     */
    @Override
    public List<Entry> loadAttributeTypes( Schema... schemas ) throws LdapException, IOException
    {
        List<Entry> attributeTypeEntries = new ArrayList<>();

        if ( schemas == null )
        {
            return attributeTypeEntries;
        }

        AttributesFactory factory = new AttributesFactory();

        for ( Schema schema : schemas )
        {
            Set<SchemaObjectWrapper> schemaObjectWrappers = schema.getContent();

            for ( SchemaObjectWrapper schemaObjectWrapper : schemaObjectWrappers )
            {
                SchemaObject schemaObject = schemaObjectWrapper.get();

                if ( schemaObject instanceof AttributeType )
                {
                    AttributeType attributeType = ( AttributeType ) schemaObject;

                    Entry attributeTypeEntry = factory.convert( attributeType, schema, null );

                    attributeTypeEntries.add( attributeTypeEntry );
                }
            }
        }

        return attributeTypeEntries;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public List<Entry> loadComparators( Schema... schemas ) throws LdapException, IOException
    {
        List<Entry> comparatorEntries = new ArrayList<>();

        if ( schemas == null )
        {
            return comparatorEntries;
        }

        for ( Schema schema : schemas )
        {
            Set<SchemaObjectWrapper> schemaObjectWrappers = schema.getContent();

            for ( SchemaObjectWrapper schemaObjectWrapper : schemaObjectWrappers )
            {
                SchemaObject schemaObject = schemaObjectWrapper.get();

                if ( schemaObject instanceof LdapComparatorDescription )
                {
                    LdapComparatorDescription ldapComparatorDescription = ( LdapComparatorDescription ) schemaObject;
                    Entry lcEntry = getEntry( ldapComparatorDescription );

                    comparatorEntries.add( lcEntry );
                }
            }
        }

        return comparatorEntries;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public List<Entry> loadDitContentRules( Schema... schemas ) throws LdapException, IOException
    {
        List<Entry> ditContentRuleEntries = new ArrayList<>();

        if ( schemas == null )
        {
            return ditContentRuleEntries;
        }

        AttributesFactory factory = new AttributesFactory();

        for ( Schema schema : schemas )
        {
            Set<SchemaObjectWrapper> schemaObjectWrappers = schema.getContent();

            for ( SchemaObjectWrapper schemaObjectWrapper : schemaObjectWrappers )
            {
                SchemaObject schemaObject = schemaObjectWrapper.get();

                if ( schemaObject instanceof DitContentRule )
                {
                    DitContentRule ditContentRule = ( DitContentRule ) schemaObject;

                    Entry ditContentRuleEntry = factory.convert( ditContentRule, schema, null );

                    ditContentRuleEntries.add( ditContentRuleEntry );
                }
            }
        }

        return ditContentRuleEntries;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public List<Entry> loadDitStructureRules( Schema... schemas ) throws LdapException, IOException
    {
        List<Entry> ditStructureRuleEntries = new ArrayList<>();

        if ( schemas == null )
        {
            return ditStructureRuleEntries;
        }

        AttributesFactory factory = new AttributesFactory();

        for ( Schema schema : schemas )
        {
            Set<SchemaObjectWrapper> schemaObjectWrappers = schema.getContent();

            for ( SchemaObjectWrapper schemaObjectWrapper : schemaObjectWrappers )
            {
                SchemaObject schemaObject = schemaObjectWrapper.get();

                if ( schemaObject instanceof DitStructureRule )
                {
                    DitStructureRule ditStructureRule = ( DitStructureRule ) schemaObject;

                    Entry ditStructureRuleEntry = factory.convert( ditStructureRule, schema, null );

                    ditStructureRuleEntries.add( ditStructureRuleEntry );
                }
            }
        }

        return ditStructureRuleEntries;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public List<Entry> loadMatchingRuleUses( Schema... schemas ) throws LdapException, IOException
    {
        List<Entry> matchingRuleUseEntries = new ArrayList<>();

        if ( schemas == null )
        {
            return matchingRuleUseEntries;
        }

        AttributesFactory factory = new AttributesFactory();

        for ( Schema schema : schemas )
        {
            Set<SchemaObjectWrapper> schemaObjectWrappers = schema.getContent();

            for ( SchemaObjectWrapper schemaObjectWrapper : schemaObjectWrappers )
            {
                SchemaObject schemaObject = schemaObjectWrapper.get();

                if ( schemaObject instanceof MatchingRuleUse )
                {
                    MatchingRuleUse matchingRuleUse = ( MatchingRuleUse ) schemaObject;

                    Entry matchingRuleUseEntry = factory.convert( matchingRuleUse, schema, null );

                    matchingRuleUseEntries.add( matchingRuleUseEntry );
                }
            }
        }

        return matchingRuleUseEntries;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public List<Entry> loadMatchingRules( Schema... schemas ) throws LdapException, IOException
    {
        List<Entry> matchingRuleEntries = new ArrayList<>();

        if ( schemas == null )
        {
            return matchingRuleEntries;
        }

        AttributesFactory factory = new AttributesFactory();

        for ( Schema schema : schemas )
        {
            Set<SchemaObjectWrapper> schemaObjectWrappers = schema.getContent();

            for ( SchemaObjectWrapper schemaObjectWrapper : schemaObjectWrappers )
            {
                SchemaObject schemaObject = schemaObjectWrapper.get();

                if ( schemaObject instanceof MatchingRule )
                {
                    MatchingRule matchingRule = ( MatchingRule ) schemaObject;

                    Entry matchingRuleEntry = factory.convert( matchingRule, schema, null );

                    matchingRuleEntries.add( matchingRuleEntry );
                }
            }
        }

        return matchingRuleEntries;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public List<Entry> loadNameForms( Schema... schemas ) throws LdapException, IOException
    {
        List<Entry> nameFormEntries = new ArrayList<>();

        if ( schemas == null )
        {
            return nameFormEntries;
        }

        AttributesFactory factory = new AttributesFactory();

        for ( Schema schema : schemas )
        {
            Set<SchemaObjectWrapper> schemaObjectWrappers = schema.getContent();

            for ( SchemaObjectWrapper schemaObjectWrapper : schemaObjectWrappers )
            {
                SchemaObject schemaObject = schemaObjectWrapper.get();

                if ( schemaObject instanceof NameForm )
                {
                    NameForm nameForm = ( NameForm ) schemaObject;

                    Entry nameFormEntry = factory.convert( nameForm, schema, null );

                    nameFormEntries.add( nameFormEntry );
                }
            }
        }

        return nameFormEntries;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public List<Entry> loadNormalizers( Schema... schemas ) throws LdapException, IOException
    {
        List<Entry> normalizerEntries = new ArrayList<>();

        if ( schemas == null )
        {
            return normalizerEntries;
        }

        for ( Schema schema : schemas )
        {
            Set<SchemaObjectWrapper> schemaObjectWrappers = schema.getContent();

            for ( SchemaObjectWrapper schemaObjectWrapper : schemaObjectWrappers )
            {
                SchemaObject schemaObject = schemaObjectWrapper.get();

                if ( schemaObject instanceof NormalizerDescription )
                {
                    NormalizerDescription normalizerDescription = ( NormalizerDescription ) schemaObject;
                    Entry normalizerEntry = getEntry( normalizerDescription );

                    normalizerEntries.add( normalizerEntry );
                }
            }
        }

        return normalizerEntries;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public List<Entry> loadObjectClasses( Schema... schemas ) throws LdapException, IOException
    {
        List<Entry> objectClassEntries = new ArrayList<>();

        if ( schemas == null )
        {
            return objectClassEntries;
        }

        AttributesFactory factory = new AttributesFactory();

        for ( Schema schema : schemas )
        {
            Set<SchemaObjectWrapper> schemaObjectWrappers = schema.getContent();

            for ( SchemaObjectWrapper schemaObjectWrapper : schemaObjectWrappers )
            {
                SchemaObject schemaObject = schemaObjectWrapper.get();

                if ( schemaObject instanceof ObjectClass )
                {
                    ObjectClass objectClass = ( ObjectClass ) schemaObject;

                    Entry objectClassEntry = factory.convert( objectClass, schema, null );

                    objectClassEntries.add( objectClassEntry );
                }
            }
        }

        return objectClassEntries;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public List<Entry> loadSyntaxCheckers( Schema... schemas ) throws LdapException, IOException
    {
        List<Entry> syntaxCheckerEntries = new ArrayList<>();

        if ( schemas == null )
        {
            return syntaxCheckerEntries;
        }

        for ( Schema schema : schemas )
        {
            Set<SchemaObjectWrapper> schemaObjectWrappers = schema.getContent();

            for ( SchemaObjectWrapper schemaObjectWrapper : schemaObjectWrappers )
            {
                SchemaObject schemaObject = schemaObjectWrapper.get();

                if ( schemaObject instanceof SyntaxCheckerDescription )
                {
                    SyntaxCheckerDescription syntaxCheckerDescription = ( SyntaxCheckerDescription ) schemaObject;
                    Entry syntaxCheckerEntry = getEntry( syntaxCheckerDescription );

                    syntaxCheckerEntries.add( syntaxCheckerEntry );
                }
            }
        }

        return syntaxCheckerEntries;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public List<Entry> loadSyntaxes( Schema... schemas ) throws LdapException, IOException
    {
        List<Entry> syntaxEntries = new ArrayList<>();

        if ( schemas == null )
        {
            return syntaxEntries;
        }

        AttributesFactory factory = new AttributesFactory();

        for ( Schema schema : schemas )
        {
            Set<SchemaObjectWrapper> schemaObjectWrappers = schema.getContent();

            for ( SchemaObjectWrapper schemaObjectWrapper : schemaObjectWrappers )
            {
                SchemaObject schemaObject = schemaObjectWrapper.get();

                if ( schemaObject instanceof LdapSyntax )
                {
                    LdapSyntax ldapSyntax = ( LdapSyntax ) schemaObject;

                    Entry ldapSyntaxEntry = factory.convert( ldapSyntax, schema, null );

                    syntaxEntries.add( ldapSyntaxEntry );
                }
            }
        }

        return syntaxEntries;
    }


    private Entry getEntry( LdapComparatorDescription comparatorDescription )
    {
        Entry entry = new DefaultEntry();

        entry.put( SchemaConstants.OBJECT_CLASS_AT,
            SchemaConstants.TOP_OC,
            MetaSchemaConstants.META_TOP_OC,
            MetaSchemaConstants.META_COMPARATOR_OC );

        entry.put( MetaSchemaConstants.M_OID_AT, comparatorDescription.getOid() );
        entry.put( MetaSchemaConstants.M_FQCN_AT, comparatorDescription.getFqcn() );

        if ( comparatorDescription.getBytecode() != null )
        {
            entry.put( MetaSchemaConstants.M_BYTECODE_AT,
                Base64.getDecoder().decode( comparatorDescription.getBytecode() ) );
        }

        if ( comparatorDescription.getDescription() != null )
        {
            entry.put( MetaSchemaConstants.M_DESCRIPTION_AT, comparatorDescription.getDescription() );
        }

        return entry;
    }


    private Entry getEntry( SyntaxCheckerDescription syntaxCheckerDescription )
    {
        Entry entry = new DefaultEntry();

        entry.put( SchemaConstants.OBJECT_CLASS_AT,
            SchemaConstants.TOP_OC,
            MetaSchemaConstants.META_TOP_OC,
            MetaSchemaConstants.META_SYNTAX_CHECKER_OC );

        entry.put( MetaSchemaConstants.M_OID_AT, syntaxCheckerDescription.getOid() );
        entry.put( MetaSchemaConstants.M_FQCN_AT, syntaxCheckerDescription.getFqcn() );

        if ( syntaxCheckerDescription.getBytecode() != null )
        {
            entry.put( MetaSchemaConstants.M_BYTECODE_AT,
                Base64.getDecoder().decode( syntaxCheckerDescription.getBytecode() ) );
        }

        if ( syntaxCheckerDescription.getDescription() != null )
        {
            entry.put( MetaSchemaConstants.M_DESCRIPTION_AT, syntaxCheckerDescription.getDescription() );
        }

        return entry;
    }


    private Entry getEntry( NormalizerDescription normalizerDescription )
    {
        Entry entry = new DefaultEntry();

        entry.put( SchemaConstants.OBJECT_CLASS_AT,
            SchemaConstants.TOP_OC,
            MetaSchemaConstants.META_TOP_OC,
            MetaSchemaConstants.META_NORMALIZER_OC );

        entry.put( MetaSchemaConstants.M_OID_AT, normalizerDescription.getOid() );
        entry.put( MetaSchemaConstants.M_FQCN_AT, normalizerDescription.getFqcn() );

        if ( normalizerDescription.getBytecode() != null )
        {
            entry.put( MetaSchemaConstants.M_BYTECODE_AT,
                Base64.getDecoder().decode( normalizerDescription.getBytecode() ) );
        }

        if ( normalizerDescription.getDescription() != null )
        {
            entry.put( MetaSchemaConstants.M_DESCRIPTION_AT, normalizerDescription.getDescription() );
        }

        return entry;
    }


    /**
     * Sets the quirks mode for all the internal parsers.
     *
     * If enabled the parser accepts non-numeric OIDs and some
     * special characters in descriptions.
     *
     * @param enabled the new quirks mode
     */
    public void setQuirksMode( boolean enabled )
    {
        AT_DESCR_SCHEMA_PARSER.setQuirksMode( enabled );
        C_DESCR_SCHEMA_PARSER.setQuirksMode( enabled );
        DCR_DESCR_SCHEMA_PARSER.setQuirksMode( enabled );
        DSR_DESCR_SCHEMA_PARSER.setQuirksMode( enabled );
        LS_DESCR_SCHEMA_PARSER.setQuirksMode( enabled );
        MR_DESCR_SCHEMA_PARSER.setQuirksMode( enabled );
        MRU_DESCR_SCHEMA_PARSER.setQuirksMode( enabled );
        N_DESCR_SCHEMA_PARSER.setQuirksMode( enabled );
        NF_DESCR_SCHEMA_PARSER.setQuirksMode( enabled );
        OC_DESCR_SCHEMA_PARSER.setQuirksMode( enabled );
        SC_DESCR_SCHEMA_PARSER.setQuirksMode( enabled );
    }
}
