/*
 *  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
 *  
 *    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. 
 *  
 */
package org.apache.directory.api.ldap.model.schema;


import java.util.List;
import java.util.Map;


/**
 * Utility class used to generate schema object specifications. Some of the
 * latest work coming out of the LDAPBIS working body adds optional extensions
 * to these syntaxes. Descriptions can be generated for
 * the following objects:
 * <ul>
 * <li><a href="./AttributeType.html">AttributeType</a></li>
 * <li><a href="./DitContentRule.html">DitContentRule</a></li>
 * <li><a href="./DitContentRule.html">DitStructureRule</a></li>
 * <li><a href="./LdapComparator.html">Syntax</a></li>
 * <li><a href="./MatchingRule.html">MatchingRule</a></li>
 * <li><a href="./MatchingRuleUse.html">MatchingRuleUse</a></li>
 * <li><a href="./NameForm.html">NameForm</a></li>
 * <li><a href="./Normalizer.html">Syntax</a></li>
 * <li><a href="./ObjectClass.html">ObjectClass</a></li>
 * <li><a href="./LdapSyntax.html">Syntax</a></li>
 * <li><a href="./SyntaxChecker.html">Syntax</a></li>
 * </ul>
 * 
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public final class DescriptionUtils
{
    /**
     * Private constructor.
     */
    private DescriptionUtils()
    {
    }


    /**
     * Generates the description using the AttributeTypeDescription as defined
     * by the syntax: 1.3.6.1.4.1.1466.115.121.1.3. Only the right hand side of
     * the description starting at the opening parenthesis is generated: that
     * is 'AttributeTypeDescription = ' is not generated.
     * 
     * <pre>
     *  AttributeTypeDescription = &quot;(&quot; whsp
     *     numericoid whsp                ; AttributeType identifier
     *     [ &quot;NAME&quot; qdescrs ]             ; name used in AttributeType
     *     [ &quot;DESC&quot; qdstring ]            ; description
     *     [ &quot;OBSOLETE&quot; whsp ]
     *     [ &quot;SUP&quot; woid ]                 ; derived from parent AttributeType
     *     [ &quot;EQUALITY&quot; woid              ; Matching Rule name
     *     [ &quot;ORDERING&quot; woid              ; Matching Rule name
     *     [ &quot;SUBSTR&quot; woid ]              ; Matching Rule name
     *     [ &quot;SYNTAX&quot; whsp noidlen whsp ] ; see section 4.3 RFC 2252
     *     [ &quot;SINGLE-VALUE&quot; whsp ]        ; default multi-valued
     *     [ &quot;COLLECTIVE&quot; whsp ]          ; default not collective
     *     [ &quot;NO-USER-MODIFICATION&quot; whsp ]; default user modifiable
     *     [ &quot;USAGE&quot; whsp AttributeUsage ]; default userApplications
     *     whsp &quot;)&quot;
     * </pre>
     * 
     * @param attributeType
     *            the attributeType to generate a description for
     * @return the AttributeTypeDescription Syntax for the attributeType in a
     *         pretty formated string
     */
    public static String getDescription( AttributeType attributeType )
    {
        StringBuilder buf = new StringBuilder( "( " );
        buf.append( attributeType.getOid() );
        buf.append( '\n' );

        if ( attributeType.getNames().size() != 0 )
        {
            buf.append( " NAME " );
            getQDescrs( buf, attributeType.getNames() );
        }

        if ( attributeType.getDescription() != null )
        {
            buf.append( " DESC " );
            buf.append( attributeType.getDescription() );
            buf.append( '\n' );
        }

        if ( attributeType.isObsolete() )
        {
            buf.append( " OBSOLETE\n" );
        }

        if ( attributeType.getSuperior() != null )
        {
            buf.append( " SUP " );
            buf.append( attributeType.getSuperiorName() );
            buf.append( '\n' );
        }

        if ( attributeType.getEquality() != null )
        {
            buf.append( " EQUALITY " );
            buf.append( attributeType.getEqualityName() );
            buf.append( '\n' );
        }

        if ( attributeType.getOrdering() != null )
        {
            buf.append( " ORDERING " );
            buf.append( attributeType.getOrderingName() );
            buf.append( '\n' );
        }

        if ( attributeType.getSubstring() != null )
        {
            buf.append( " SUBSTR " );
            buf.append( attributeType.getSubstringName() );
            buf.append( '\n' );
        }

        if ( attributeType.getSyntax() != null )
        {
            buf.append( " SYNTAX " );

            buf.append( attributeType.getSyntaxName() );

            if ( attributeType.getSyntaxLength() > 0 )
            {
                buf.append( '{' ).append( attributeType.getSyntaxLength() ).append( '}' );
            }

            buf.append( '\n' );
        }

        if ( attributeType.isSingleValued() )
        {
            buf.append( " SINGLE-VALUE\n" );
        }

        if ( attributeType.isCollective() )
        {
            buf.append( " COLLECTIVE\n" );
        }

        if ( !attributeType.isUserModifiable() )
        {
            buf.append( " NO-USER-MODIFICATION\n" );
        }

        buf.append( " USAGE " );
        buf.append( UsageEnum.render( attributeType.getUsage() ) );
        buf.append( '\n' );

        if ( attributeType.getExtensions() != null )
        {
            getExtensions( buf, attributeType.getExtensions() );
        }

        buf.append( " )\n" );

        return buf.toString();
    }


    /**
     * Generates the ComparatorDescription for a LdapComparator. Only the right 
     * hand side of the description starting at the opening parenthesis is 
     * generated: that is 'ComparatorDescription = ' is not generated.
     * 
     * <pre>
     * ComparatorDescription = &quot;(&quot;
     *     numericoid                          
     *     [&quot;DESC&quot; qdstring ]
     *     &quot;FQCN&quot; whsp fqcn
     *     [&quot;BYTECODE&quot; whsp base64  ]
     *     extensions 
     *     &quot;)&quot;
     * </pre>
     * 
     * @param comparator
     *            the Comparator to generate the description for
     * @return the ComparatorDescription string
     */
    public static String getDescription( LdapComparator<?> comparator )
    {
        return getLoadableDescription( comparator );
    }


    /**
     * Generates the DITContentRuleDescription for a DitContentRule as defined
     * by the syntax: 1.3.6.1.4.1.1466.115.121.1.16. Only the right hand side of
     * the description starting at the opening parenthesis is generated: that
     * is 'DITContentRuleDescription = ' is not generated.
     * 
     * <pre>
     *   DITContentRuleDescription = &quot;(&quot;
     *       numericoid         ; Structural ObjectClass identifier
     *       [ &quot;NAME&quot; qdescrs ]
     *       [ &quot;DESC&quot; qdstring ]
     *       [ &quot;OBSOLETE&quot; ]
     *       [ &quot;AUX&quot; oids ]     ; Auxiliary ObjectClasses
     *       [ &quot;MUST&quot; oids ]    ; AttributeType identifiers
     *       [ &quot;MAY&quot; oids ]     ; AttributeType identifiers
     *       [ &quot;NOT&quot; oids ]     ; AttributeType identifiers
     *      &quot;)&quot;
     * </pre>
     * 
     * @param dITContentRule
     *            the DIT content rule specification
     * @return the specification according to the DITContentRuleDescription
     *         syntax
     */
    public static String getDescription( DitContentRule dITContentRule )
    {
        StringBuilder buf = new StringBuilder( "( " );
        buf.append( dITContentRule.getOid() );
        buf.append( '\n' );

        if ( dITContentRule.getNames() != null )
        {
            buf.append( " NAME " );
            getQDescrs( buf, dITContentRule.getNames() );
            buf.append( '\n' );
        }

        if ( dITContentRule.getDescription() != null )
        {
            buf.append( " DESC " );
            buf.append( dITContentRule.getDescription() );
            buf.append( '\n' );
        }

        if ( dITContentRule.isObsolete() )
        {
            buf.append( " OBSOLETE\n" );
        }

        // print out all the auxiliary object class oids
        List<ObjectClass> aux = dITContentRule.getAuxObjectClasses();

        if ( ( aux != null ) && ( aux.size() > 0 ) )
        {
            buf.append( " AUX " );
            getQDStrings( buf, aux );
        }

        List<AttributeType> must = dITContentRule.getMustAttributeTypes();

        if ( ( must != null ) && ( must.size() > 0 ) )
        {
            buf.append( " MUST " );
            getQDStrings( buf, must );
        }

        List<AttributeType> may = dITContentRule.getMayAttributeTypes();

        if ( ( may != null ) && ( may.size() > 0 ) )
        {
            buf.append( " MAY " );
            getQDStrings( buf, may );
        }

        List<AttributeType> not = dITContentRule.getNotAttributeTypes();

        if ( ( not != null ) && ( not.size() > 0 ) )
        {
            buf.append( " NOT " );
            getQDStrings( buf, not );
        }

        if ( dITContentRule.getExtensions() != null )
        {
            getExtensions( buf, dITContentRule.getExtensions() );
        }

        buf.append( " )\n" );
        return buf.toString();
    }


    /**
     * Generates the DITStructureRuleDescription for a DitStructureRule as
     * defined by the syntax: 1.3.6.1.4.1.1466.115.121.1.17. Only the right hand
     * side of the description starting at the opening parenthesis is
     * generated: that is 'DITStructureRuleDescription = ' is not generated.
     * 
     * <pre>
     *   DITStructureRuleDescription = &quot;(&quot; whsp
     *       ruleid                     ; rule identifier
     *       [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
     *       [ SP "DESC" SP qdstring ]  ; description
     *       [ SP "OBSOLETE" ]          ; not active
     *       SP "FORM" SP oid           ; NameForm
     *       [ SP "SUP" ruleids ]       ; superior rules
     *       extensions WSP             ; extensions
     *       &quot;)&quot;
     * </pre>
     * 
     * @param ditStructureRule
     *            the DitStructureRule to generate the description for
     * @return the description in the DITStructureRuleDescription syntax
     */
    public static String getDescription( DitStructureRule ditStructureRule )
    {
        StringBuilder buf = new StringBuilder( "( " );
        buf.append( ditStructureRule.getOid() );
        buf.append( '\n' );

        if ( ditStructureRule.getNames() != null )
        {
            buf.append( " NAME " );
            getQDescrs( buf, ditStructureRule.getNames() );
        }

        if ( ditStructureRule.getDescription() != null )
        {
            buf.append( " DESC " );
            buf.append( ditStructureRule.getDescription() );
            buf.append( '\n' );
        }

        if ( ditStructureRule.isObsolete() )
        {
            buf.append( " OBSOLETE\n" );
        }

        buf.append( " FORM " );
        buf.append( ditStructureRule.getForm() );
        buf.append( '\n' );

        // TODO : Shouldn't we get the ruleId OID ? 
        List<Integer> sups = ditStructureRule.getSuperRules();

        if ( ( sups != null ) && ( sups.size() > 0 ) )
        {
            buf.append( " SUP\n" );

            if ( sups.size() == 1 )
            {
                buf.append( sups.get( 0 ) );
            }
            else
            {
                boolean isFirst = true;
                buf.append( "( " );

                for ( int sup : sups )
                {
                    if ( isFirst )
                    {
                        isFirst = false;
                    }
                    else
                    {
                        buf.append( " " );
                    }

                    buf.append( sup );
                }

                buf.append( " )" );
            }

            buf.append( '\n' );
        }

        buf.append( " )\n" );

        return buf.toString();
    }


    /**
     * Generates the MatchingRuleDescription for a MatchingRule as defined by
     * the syntax: 1.3.6.1.4.1.1466.115.121.1.30. Only the right hand side of
     * the description starting at the opening parenthesis is generated: that
     * is 'MatchingRuleDescription = ' is not generated.
     * 
     * <pre>
     *  MatchingRuleDescription = &quot;(&quot; whsp
     *     numericoid whsp      ; MatchingRule object identifier
     *     [ &quot;NAME&quot; qdescrs ]
     *     [ &quot;DESC&quot; qdstring ]
     *     [ &quot;OBSOLETE&quot; whsp ]
     *     &quot;SYNTAX&quot; numericoid
     *  whsp &quot;)&quot;
     * </pre>
     * 
     * @param matchingRule
     *            the MatchingRule to generate the description for
     * @return the MatchingRuleDescription string
     */
    public static String getDescription( MatchingRule matchingRule )
    {
        StringBuilder buf = new StringBuilder( "( " );
        buf.append( matchingRule.getOid() );
        buf.append( '\n' );

        if ( matchingRule.getNames() != null )
        {
            buf.append( " NAME " );
            getQDescrs( buf, matchingRule.getNames() );
        }

        if ( matchingRule.getDescription() != null )
        {
            buf.append( " DESC " );
            buf.append( matchingRule.getDescription() );
            buf.append( '\n' );
        }

        if ( matchingRule.isObsolete() )
        {
            buf.append( " OBSOLETE\n" );
        }

        buf.append( " SYNTAX " );
        buf.append( matchingRule.getSyntaxOid() );
        buf.append( '\n' );

        if ( matchingRule.getExtensions() != null )
        {
            getExtensions( buf, matchingRule.getExtensions() );
        }

        buf.append( " ) " );
        return buf.toString();
    }


    /**
     * Generates the MatchingRuleUseDescription for a MatchingRuleUse as defined
     * by the syntax: 1.3.6.1.4.1.1466.115.121.1.31. Only the right hand side of
     * the description starting at the opening parenthesis is generated: that
     * is 'MatchingRuleUseDescription = ' is not generated.
     * 
     * <pre>
     *      MatchingRuleUseDescription = LPAREN WSP
     *          numericoid                ; object identifier
     *          [ SP &quot;NAME&quot; SP qdescrs ]  ; short names (descriptors)
     *          [ SP &quot;DESC&quot; SP qdstring ] ; description
     *          [ SP &quot;OBSOLETE&quot; ]         ; not active
     *          SP &quot;APPLIES&quot; SP oids      ; attribute types
     *          extensions WSP RPAREN     ; extensions
     *  
     *    where:
     *      [numericoid] is the object identifier of the matching rule
     *          associated with this matching rule use description;
     *      NAME [qdescrs] are short names (descriptors) identifying this
     *          matching rule use;
     *      DESC [qdstring] is a short descriptive string;
     *      OBSOLETE indicates this matching rule use is not active;
     *      APPLIES provides a list of attribute types the matching rule applies
     *          to; and
     *      [extensions] describe extensions.
     * </pre>
     * 
     * @param matchingRuleUse The matching rule from which we want to generate
     *  a MatchingRuleUseDescription.
     * @return The generated MatchingRuleUseDescription
     */
    public static String getDescription( MatchingRuleUse matchingRuleUse )
    {
        StringBuilder buf = new StringBuilder( "( " );
        buf.append( matchingRuleUse.getOid() );
        buf.append( '\n' );

        buf.append( " NAME " );
        getQDescrs( buf, matchingRuleUse.getNames() );

        if ( matchingRuleUse.getDescription() != null )
        {
            buf.append( " DESC " );
            buf.append( matchingRuleUse.getDescription() );
            buf.append( '\n' );
        }

        if ( matchingRuleUse.isObsolete() )
        {
            buf.append( " OBSOLETE\n" );
        }

        buf.append( " APPLIES " );
        List<AttributeType> attributeTypes = matchingRuleUse.getApplicableAttributes();

        if ( attributeTypes.size() == 1 )
        {
            buf.append( attributeTypes.get( 0 ).getOid() );
        }
        else
        // for list of oids we need a parenthesis
        {
            buf.append( "( " );

            boolean isFirst = true;

            for ( AttributeType attributeType : attributeTypes )
            {
                if ( isFirst )
                {
                    isFirst = false;
                }
                else
                {
                    buf.append( " $ " );
                }

                buf.append( attributeType );
            }

            buf.append( " ) " );
        }

        if ( matchingRuleUse.getExtensions() != null )
        {
            getExtensions( buf, matchingRuleUse.getExtensions() );
        }

        buf.append( " )\n" );

        return buf.toString();
    }


    /**
     * Generates the NameFormDescription for a NameForm as defined by the
     * syntax: 1.3.6.1.4.1.1466.115.121.1.35. Only the right hand side of the
     * description starting at the opening parenthesis is generated: that is
     * 'NameFormDescription = ' is not generated.
     * 
     * <pre>
     *  NameFormDescription = &quot;(&quot; whsp
     *      numericoid whsp               ; NameForm identifier
     *      [ &quot;NAME&quot; qdescrs ]
     *      [ &quot;DESC&quot; qdstring ]
     *      [ &quot;OBSOLETE&quot; whsp ]
     *      &quot;OC&quot; woid                     ; Structural ObjectClass
     *      &quot;MUST&quot; oids                   ; AttributeTypes
     *      [ &quot;MAY&quot; oids ]                ; AttributeTypes
     *  whsp &quot;)&quot;
     * </pre>
     * 
     * @param nameForm
     *            the NameForm to generate the description for
     * @return the NameFormDescription string
     */
    public static String getDescription( NameForm nameForm )
    {
        StringBuilder buf = new StringBuilder( "( " );
        buf.append( nameForm.getOid() );
        buf.append( '\n' );

        if ( nameForm.getNames() != null )
        {
            buf.append( " NAME " );
            getQDescrs( buf, nameForm.getNames() );
        }

        if ( nameForm.getDescription() != null )
        {
            buf.append( " DESC " );
            buf.append( nameForm.getDescription() );
            buf.append( '\n' );
        }

        if ( nameForm.isObsolete() )
        {
            buf.append( " OBSOLETE\n" );
        }

        buf.append( " OC " );
        buf.append( nameForm.getStructuralObjectClassOid() );
        buf.append( '\n' );

        buf.append( " MUST\n" );
        List<AttributeType> must = nameForm.getMustAttributeTypes();

        getQDStrings( buf, must );

        List<AttributeType> may = nameForm.getMayAttributeTypes();

        if ( ( may != null ) && ( may.size() > 0 ) )
        {
            buf.append( " MAY\n" );
            getQDStrings( buf, may );
        }

        if ( nameForm.getExtensions() != null )
        {
            getExtensions( buf, nameForm.getExtensions() );
        }

        buf.append( " )\n" );
        return buf.toString();
    }


    /**
     * Generates the NormalizerDescription for a Normalizer. Only the right 
     * hand side of the description starting at the opening parenthesis is 
     * generated: that is 'NormalizerDescription = ' is not generated.
     * 
     * <pre>
     * NormalizerDescription = &quot;(&quot;
     *     numericoid                          
     *     [&quot;DESC&quot; qdstring ]
     *     &quot;FQCN&quot; whsp fqcn
     *     [&quot;BYTECODE&quot; whsp base64  ]
     *     extensions 
     *     &quot;)&quot;
     * </pre>
     * 
     * @param normalizer
     *            the Normalizer to generate the description for
     * @return the NormalizerDescription string
     */
    public static String getDescription( Normalizer normalizer )
    {
        return getLoadableDescription( normalizer );
    }


    /**
     * Generates the ObjectClassDescription for an ObjectClass as defined by the
     * syntax: 1.3.6.1.4.1.1466.115.121.1.37. Only the right hand side of the
     * description starting at the opening parenthesis is generated: that is
     * 'ObjectClassDescription = ' is not generated.
     * 
     * <pre>
     *  ObjectClassDescription = &quot;(&quot; whsp
     *      numericoid whsp     ; ObjectClass identifier
     *      [ &quot;NAME&quot; qdescrs ]
     *      [ &quot;DESC&quot; qdstring ]
     *      [ &quot;OBSOLETE&quot; whsp ]
     *      [ &quot;SUP&quot; oids ]      ; Superior ObjectClasses
     *      [ ( &quot;ABSTRACT&quot; / &quot;STRUCTURAL&quot; / &quot;AUXILIARY&quot; ) whsp ]
     *                          ; default structural
     *      [ &quot;MUST&quot; oids ]     ; AttributeTypes
     *      [ &quot;MAY&quot; oids ]      ; AttributeTypes
     *  whsp &quot;)&quot;
     * </pre>
     * 
     * @param objectClass
     *            the ObjectClass to generate a description for
     * @return the description in the ObjectClassDescription syntax
     */
    public static String getDescription( ObjectClass objectClass )
    {
        StringBuilder buf = new StringBuilder( "( " );
        buf.append( objectClass.getOid() );
        buf.append( '\n' );

        if ( ( objectClass.getNames() != null ) && ( objectClass.getNames().size() != 0 ) )
        {
            buf.append( " NAME " );
            getQDescrs( buf, objectClass.getNames() );
        }

        if ( objectClass.getDescription() != null )
        {
            buf.append( " DESC " );
            buf.append( objectClass.getDescription() );
            buf.append( '\n' );
        }

        if ( objectClass.isObsolete() )
        {
            buf.append( " OBSOLETE\n" );
        }

        List<ObjectClass> sups = objectClass.getSuperiors();

        if ( ( sups != null ) && ( sups.size() > 0 ) )
        {
            buf.append( " SUP " );
            getQDStrings( buf, sups );
        }

        if ( objectClass.getType() != null )
        {
            buf.append( ' ' );
            buf.append( objectClass.getType() );
            buf.append( '\n' );
        }

        List<AttributeType> must = objectClass.getMustAttributeTypes();

        if ( ( must != null ) && ( must.size() > 0 ) )
        {
            buf.append( " MUST " );
            getQDStrings( buf, must );
        }

        List<AttributeType> may = objectClass.getMayAttributeTypes();

        if ( ( may != null ) && ( may.size() > 0 ) )
        {
            buf.append( " MAY " );
            getQDStrings( buf, may );
        }

        if ( objectClass.getExtensions() != null )
        {
            getExtensions( buf, objectClass.getExtensions() );
        }

        buf.append( " )\n" );

        return buf.toString();
    }


    /**
     * Generates the SyntaxDescription for a Syntax as defined by the syntax:
     * 1.3.6.1.4.1.1466.115.121.1.54. Only the right hand side of the
     * description starting at the opening parenthesis is generated: that is
     * 'SyntaxDescription = ' is not generated.
     * 
     * <pre>
     *  SyntaxDescription = &quot;(&quot; whsp
     *      numericoid whsp
     *      [ &quot;DESC&quot; qdstring ]
     *      [ extensions ]
     *      whsp &quot;)&quot;
     * </pre>
     * 
     * @param syntax
     *            the Syntax to generate a description for
     * @return the description in the SyntaxDescription syntax
     */
    public static String getDescription( LdapSyntax syntax )
    {
        StringBuilder buf = new StringBuilder( "( " );
        buf.append( syntax.getOid() );
        buf.append( '\n' );

        if ( syntax.getDescription() != null )
        {
            buf.append( " DESC " );
            buf.append( syntax.getDescription() );
            buf.append( '\n' );
        }

        if ( syntax.getExtensions() != null )
        {
            getExtensions( buf, syntax.getExtensions() );
        }

        buf.append( " )" );
        return buf.toString();
    }


    /**
     * Generates the SyntaxCheckerDescription for a SyntaxChecker. Only the right 
     * hand side of the description starting at the opening parenthesis is 
     * generated: that is 'SyntaxCheckerDescription = ' is not generated.
     * 
     * <pre>
     * SyntaxCheckerDescription = &quot;(&quot;
     *     numericoid                          
     *     [&quot;DESC&quot; qdstring ]
     *     &quot;FQCN&quot; whsp fqcn
     *     [&quot;BYTECODE&quot; whsp base64  ]
     *     extensions 
     *     &quot;)&quot;
     * </pre>
     * 
     * @param syntaxChecker
     *            the SyntaxChecker to generate the description for
     * @return the SyntaxCheckerDescription string
     */
    public static String getDescription( SyntaxChecker syntaxChecker )
    {
        return getLoadableDescription( syntaxChecker );
    }


    private static void getExtensions( StringBuilder sb, Map<String, List<String>> extensions )
    {
        for ( Map.Entry<String, List<String>> extension : extensions.entrySet() )
        {
            sb.append( " " + extension.getKey() ).append( " " );

            List<String> values = extension.getValue();

            if ( ( values != null ) && ( values.size() != 0 ) )
            {
                if ( values.size() == 1 )
                {
                    sb.append( values.get( 0 ) );
                }
                else
                {
                    boolean isFirst = true;
                    sb.append( "( " );

                    for ( String value : values )
                    {
                        if ( isFirst )
                        {
                            isFirst = false;
                        }
                        else
                        {
                            sb.append( " " );
                        }

                        sb.append( value );
                    }

                    sb.append( " )" );
                }
            }

            sb.append( '\n' );
        }
    }


    private static void getQDStrings( StringBuilder sb, List<? extends SchemaObject> schemaObjects )
    {
        if ( ( schemaObjects != null ) && ( schemaObjects.size() != 0 ) )
        {
            if ( schemaObjects.size() == 1 )
            {
                sb.append( '\'' ).append( schemaObjects.get( 0 ).getName() ).append( '\'' );
            }
            else
            {
                boolean isFirst = true;
                sb.append( "( " );

                for ( SchemaObject schemaObject : schemaObjects )
                {
                    if ( isFirst )
                    {
                        isFirst = false;
                    }
                    else
                    {
                        sb.append( " $ " );
                    }

                    sb.append( '\'' ).append( schemaObject.getName() ).append( '\'' );
                }

                sb.append( " )" );
            }
        }

        sb.append( '\n' );
    }


    private static void getQDescrs( StringBuilder sb, List<String> names )
    {
        if ( ( names != null ) && ( names.size() != 0 ) )
        {
            if ( names.size() == 1 )
            {
                sb.append( '\'' ).append( names.get( 0 ) ).append( '\'' );
            }
            else
            {
                boolean isFirst = true;
                sb.append( "( " );

                for ( String name : names )
                {
                    if ( isFirst )
                    {
                        isFirst = false;
                    }
                    else
                    {
                        sb.append( " " );
                    }

                    sb.append( '\'' ).append( name ).append( '\'' );
                }

                sb.append( " )" );
            }
        }

        sb.append( '\n' );
    }


    /**
     * Generate the description for Comparators, Normalizers and SyntaxCheckers.
     */
    private static String getLoadableDescription( LoadableSchemaObject schemaObject )
    {
        StringBuilder buf = new StringBuilder( "( " );
        buf.append( schemaObject.getOid() );
        buf.append( '\n' );

        if ( schemaObject.getDescription() != null )
        {
            buf.append( " DESC " );
            buf.append( schemaObject.getDescription() );
            buf.append( '\n' );
        }

        if ( schemaObject.getFqcn() != null )
        {
            buf.append( " FQCN " );
            buf.append( schemaObject.getFqcn() );
            buf.append( '\n' );
        }

        if ( schemaObject.getBytecode() != null )
        {
            buf.append( " BYTECODE " );

            // We will dump only the 16 first bytes
            if ( schemaObject.getBytecode().length() > 16 )
            {
                buf.append( schemaObject.getBytecode().substring( 0, 16 ) );
            }
            else
            {
                buf.append( schemaObject.getBytecode() );
            }

            buf.append( '\n' );
        }

        if ( schemaObject.getExtensions() != null )
        {
            getExtensions( buf, schemaObject.getExtensions() );
        }

        buf.append( " ) " );

        return buf.toString();
    }
}
