001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *  
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *  
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License. 
018 *  
019 */
020package org.apache.directory.shared.ldap.model.schema;
021
022
023import java.util.List;
024import java.util.Map;
025
026
027/**
028 * Utility class used to generate schema object specifications. Some of the
029 * latest work coming out of the LDAPBIS working body adds optional extensions
030 * to these syntaxes. Descriptions can be generated for
031 * the following objects:
032 * <ul>
033 * <li><a href="./AttributeType.html">AttributeType</a></li>
034 * <li><a href="./DITContentRule.html">DITContentRule</a></li>
035 * <li><a href="./DITContentRule.html">DITStructureRule</a></li>
036 * <li><a href="./LdapComparator.html">Syntax</a></li>
037 * <li><a href="./MatchingRule.html">MatchingRule</a></li>
038 * <li><a href="./MatchingRuleUse.html">MatchingRuleUse</a></li>
039 * <li><a href="./NameForm.html">NameForm</a></li>
040 * <li><a href="./Normalizer.html">Syntax</a></li>
041 * <li><a href="./ObjectClass.html">ObjectClass</a></li>
042 * <li><a href="./LdapSyntax.html">Syntax</a></li>
043 * <li><a href="./SyntaxChecker.html">Syntax</a></li>
044 * </ul>
045 * 
046 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
047 */
048public final class DescriptionUtils
049{
050    /**
051     * Private constructor.
052     */
053    private DescriptionUtils()
054    {
055    }
056
057
058    /**
059     * Generates the description using the AttributeTypeDescription as defined
060     * by the syntax: 1.3.6.1.4.1.1466.115.121.1.3. Only the right hand side of
061     * the description starting at the opening parenthesis is generated: that
062     * is 'AttributeTypeDescription = ' is not generated.
063     * 
064     * <pre>
065     *  AttributeTypeDescription = &quot;(&quot; whsp
066     *     numericoid whsp                ; AttributeType identifier
067     *     [ &quot;NAME&quot; qdescrs ]             ; name used in AttributeType
068     *     [ &quot;DESC&quot; qdstring ]            ; description
069     *     [ &quot;OBSOLETE&quot; whsp ]
070     *     [ &quot;SUP&quot; woid ]                 ; derived from parent AttributeType
071     *     [ &quot;EQUALITY&quot; woid              ; Matching Rule name
072     *     [ &quot;ORDERING&quot; woid              ; Matching Rule name
073     *     [ &quot;SUBSTR&quot; woid ]              ; Matching Rule name
074     *     [ &quot;SYNTAX&quot; whsp noidlen whsp ] ; see section 4.3 RFC 2252
075     *     [ &quot;SINGLE-VALUE&quot; whsp ]        ; default multi-valued
076     *     [ &quot;COLLECTIVE&quot; whsp ]          ; default not collective
077     *     [ &quot;NO-USER-MODIFICATION&quot; whsp ]; default user modifiable
078     *     [ &quot;USAGE&quot; whsp AttributeUsage ]; default userApplications
079     *     whsp &quot;)&quot;
080     * </pre>
081     * 
082     * @param attributeType
083     *            the attributeType to generate a description for
084     * @return the AttributeTypeDescription Syntax for the attributeType in a
085     *         pretty formated string
086     */
087    public static String getDescription( AttributeType attributeType )
088    {
089        StringBuilder buf = new StringBuilder( "( " );
090        buf.append( attributeType.getOid() );
091        buf.append( '\n' );
092
093        if ( attributeType.getNames().size() != 0 )
094        {
095            buf.append( " NAME " );
096            getQDescrs( buf, attributeType.getNames() );
097        }
098
099        if ( attributeType.getDescription() != null )
100        {
101            buf.append( " DESC " );
102            buf.append( attributeType.getDescription() );
103            buf.append( '\n' );
104        }
105
106        if ( attributeType.isObsolete() )
107        {
108            buf.append( " OBSOLETE\n" );
109        }
110
111        if ( attributeType.getSuperior() != null )
112        {
113            buf.append( " SUP " );
114            buf.append( attributeType.getSuperiorName() );
115            buf.append( '\n' );
116        }
117
118        if ( attributeType.getEquality() != null )
119        {
120            buf.append( " EQUALITY " );
121            buf.append( attributeType.getEqualityName() );
122            buf.append( '\n' );
123        }
124
125        if ( attributeType.getOrdering() != null )
126        {
127            buf.append( " ORDERING " );
128            buf.append( attributeType.getOrderingName() );
129            buf.append( '\n' );
130        }
131
132        if ( attributeType.getSubstring() != null )
133        {
134            buf.append( " SUBSTR " );
135            buf.append( attributeType.getSubstringName() );
136            buf.append( '\n' );
137        }
138
139        if ( attributeType.getSyntax() != null )
140        {
141            buf.append( " SYNTAX " );
142
143            buf.append( attributeType.getSyntaxName() );
144
145            if ( attributeType.getSyntaxLength() > 0 )
146            {
147                buf.append( '{' ).append( attributeType.getSyntaxLength() ).append( '}' );
148            }
149
150            buf.append( '\n' );
151        }
152
153        if ( attributeType.isSingleValued() )
154        {
155            buf.append( " SINGLE-VALUE\n" );
156        }
157
158        if ( attributeType.isCollective() )
159        {
160            buf.append( " COLLECTIVE\n" );
161        }
162
163        if ( !attributeType.isUserModifiable() )
164        {
165            buf.append( " NO-USER-MODIFICATION\n" );
166        }
167
168        buf.append( " USAGE " );
169        buf.append( UsageEnum.render( attributeType.getUsage() ) );
170        buf.append( '\n' );
171
172        if ( attributeType.getExtensions() != null )
173        {
174            getExtensions( buf, attributeType.getExtensions() );
175        }
176
177        buf.append( " )\n" );
178
179        return buf.toString();
180    }
181
182
183    /**
184     * Generates the ComparatorDescription for a LdapComparator. Only the right 
185     * hand side of the description starting at the opening parenthesis is 
186     * generated: that is 'ComparatorDescription = ' is not generated.
187     * 
188     * <pre>
189     * ComparatorDescription = &quot;(&quot;
190     *     numericoid                          
191     *     [&quot;DESC&quot; qdstring ]
192     *     &quot;FQCN&quot; whsp fqcn
193     *     [&quot;BYTECODE&quot; whsp base64  ]
194     *     extensions 
195     *     &quot;)&quot;
196     * </pre>
197     * 
198     * @param comparator
199     *            the Comparator to generate the description for
200     * @return the ComparatorDescription string
201     */
202    public static String getDescription( LdapComparator<?> comparator )
203    {
204        return getLoadableDescription( comparator );
205    }
206
207
208    /**
209     * Generates the DITContentRuleDescription for a DITContentRule as defined
210     * by the syntax: 1.3.6.1.4.1.1466.115.121.1.16. Only the right hand side of
211     * the description starting at the opening parenthesis is generated: that
212     * is 'DITContentRuleDescription = ' is not generated.
213     * 
214     * <pre>
215     *   DITContentRuleDescription = &quot;(&quot;
216     *       numericoid         ; Structural ObjectClass identifier
217     *       [ &quot;NAME&quot; qdescrs ]
218     *       [ &quot;DESC&quot; qdstring ]
219     *       [ &quot;OBSOLETE&quot; ]
220     *       [ &quot;AUX&quot; oids ]     ; Auxiliary ObjectClasses
221     *       [ &quot;MUST&quot; oids ]    ; AttributeType identifiers
222     *       [ &quot;MAY&quot; oids ]     ; AttributeType identifiers
223     *       [ &quot;NOT&quot; oids ]     ; AttributeType identifiers
224     *      &quot;)&quot;
225     * </pre>
226     * 
227     * @param dITContentRule
228     *            the DIT content rule specification
229     * @return the specification according to the DITContentRuleDescription
230     *         syntax
231     */
232    public static String getDescription( DITContentRule dITContentRule )
233    {
234        StringBuilder buf = new StringBuilder( "( " );
235        buf.append( dITContentRule.getOid() );
236        buf.append( '\n' );
237
238        if ( dITContentRule.getNames() != null )
239        {
240            buf.append( " NAME " );
241            getQDescrs( buf, dITContentRule.getNames() );
242            buf.append( '\n' );
243        }
244
245        if ( dITContentRule.getDescription() != null )
246        {
247            buf.append( " DESC " );
248            buf.append( dITContentRule.getDescription() );
249            buf.append( '\n' );
250        }
251
252        if ( dITContentRule.isObsolete() )
253        {
254            buf.append( " OBSOLETE\n" );
255        }
256
257        // print out all the auxiliary object class oids
258        List<ObjectClass> aux = dITContentRule.getAuxObjectClasses();
259
260        if ( ( aux != null ) && ( aux.size() > 0 ) )
261        {
262            buf.append( " AUX " );
263            getQDStrings( buf, aux );
264        }
265
266        List<AttributeType> must = dITContentRule.getMustAttributeTypes();
267
268        if ( ( must != null ) && ( must.size() > 0 ) )
269        {
270            buf.append( " MUST " );
271            getQDStrings( buf, must );
272        }
273
274        List<AttributeType> may = dITContentRule.getMayAttributeTypes();
275
276        if ( ( may != null ) && ( may.size() > 0 ) )
277        {
278            buf.append( " MAY " );
279            getQDStrings( buf, may );
280        }
281
282        List<AttributeType> not = dITContentRule.getNotAttributeTypes();
283
284        if ( ( not != null ) && ( not.size() > 0 ) )
285        {
286            buf.append( " NOT " );
287            getQDStrings( buf, not );
288        }
289
290        if ( dITContentRule.getExtensions() != null )
291        {
292            getExtensions( buf, dITContentRule.getExtensions() );
293        }
294
295        buf.append( " )\n" );
296        return buf.toString();
297    }
298
299
300    /**
301     * Generates the DITStructureRuleDescription for a DITStructureRule as
302     * defined by the syntax: 1.3.6.1.4.1.1466.115.121.1.17. Only the right hand
303     * side of the description starting at the opening parenthesis is
304     * generated: that is 'DITStructureRuleDescription = ' is not generated.
305     * 
306     * <pre>
307     *   DITStructureRuleDescription = &quot;(&quot; whsp
308     *       ruleid                     ; rule identifier
309     *       [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
310     *       [ SP "DESC" SP qdstring ]  ; description
311     *       [ SP "OBSOLETE" ]          ; not active
312     *       SP "FORM" SP oid           ; NameForm
313     *       [ SP "SUP" ruleids ]       ; superior rules
314     *       extensions WSP             ; extensions
315     *       &quot;)&quot;
316     * </pre>
317     * 
318     * @param dITStructureRule
319     *            the DITStructureRule to generate the description for
320     * @return the description in the DITStructureRuleDescription syntax
321     */
322    public static String getDescription( DITStructureRule dITStructureRule )
323    {
324        StringBuilder buf = new StringBuilder( "( " );
325        buf.append( dITStructureRule.getOid() );
326        buf.append( '\n' );
327
328        if ( dITStructureRule.getNames() != null )
329        {
330            buf.append( " NAME " );
331            getQDescrs( buf, dITStructureRule.getNames() );
332        }
333
334        if ( dITStructureRule.getDescription() != null )
335        {
336            buf.append( " DESC " );
337            buf.append( dITStructureRule.getDescription() );
338            buf.append( '\n' );
339        }
340
341        if ( dITStructureRule.isObsolete() )
342        {
343            buf.append( " OBSOLETE\n" );
344        }
345
346        buf.append( " FORM " );
347        buf.append( dITStructureRule.getForm() );
348        buf.append( '\n' );
349
350        // TODO : Shouldn't we get the ruleId OID ? 
351        List<Integer> sups = dITStructureRule.getSuperRules();
352
353        if ( ( sups != null ) && ( sups.size() > 0 ) )
354        {
355            buf.append( " SUP\n" );
356
357            if ( sups.size() == 1 )
358            {
359                buf.append( sups.get( 0 ) );
360            }
361            else
362            {
363                boolean isFirst = true;
364                buf.append( "( " );
365
366                for ( int sup : sups )
367                {
368                    if ( isFirst )
369                    {
370                        isFirst = false;
371                    }
372                    else
373                    {
374                        buf.append( " " );
375                    }
376
377                    buf.append( sup );
378                }
379
380                buf.append( " )" );
381            }
382
383            buf.append( '\n' );
384        }
385
386        buf.append( " )\n" );
387
388        return buf.toString();
389    }
390
391
392    /**
393     * Generates the MatchingRuleDescription for a MatchingRule as defined by
394     * the syntax: 1.3.6.1.4.1.1466.115.121.1.30. Only the right hand side of
395     * the description starting at the opening parenthesis is generated: that
396     * is 'MatchingRuleDescription = ' is not generated.
397     * 
398     * <pre>
399     *  MatchingRuleDescription = &quot;(&quot; whsp
400     *     numericoid whsp      ; MatchingRule object identifier
401     *     [ &quot;NAME&quot; qdescrs ]
402     *     [ &quot;DESC&quot; qdstring ]
403     *     [ &quot;OBSOLETE&quot; whsp ]
404     *     &quot;SYNTAX&quot; numericoid
405     *  whsp &quot;)&quot;
406     * </pre>
407     * 
408     * @param matchingRule
409     *            the MatchingRule to generate the description for
410     * @return the MatchingRuleDescription string
411     */
412    public static String getDescription( MatchingRule matchingRule )
413    {
414        StringBuilder buf = new StringBuilder( "( " );
415        buf.append( matchingRule.getOid() );
416        buf.append( '\n' );
417
418        if ( matchingRule.getNames() != null )
419        {
420            buf.append( " NAME " );
421            getQDescrs( buf, matchingRule.getNames() );
422        }
423
424        if ( matchingRule.getDescription() != null )
425        {
426            buf.append( " DESC " );
427            buf.append( matchingRule.getDescription() );
428            buf.append( '\n' );
429        }
430
431        if ( matchingRule.isObsolete() )
432        {
433            buf.append( " OBSOLETE\n" );
434        }
435
436        buf.append( " SYNTAX " );
437        buf.append( matchingRule.getSyntaxOid() );
438        buf.append( '\n' );
439
440        if ( matchingRule.getExtensions() != null )
441        {
442            getExtensions( buf, matchingRule.getExtensions() );
443        }
444
445        buf.append( " ) " );
446        return buf.toString();
447    }
448
449
450    /**
451     * Generates the MatchingRuleUseDescription for a MatchingRuleUse as defined
452     * by the syntax: 1.3.6.1.4.1.1466.115.121.1.31. Only the right hand side of
453     * the description starting at the opening parenthesis is generated: that
454     * is 'MatchingRuleUseDescription = ' is not generated.
455     * 
456     * <pre>
457     *      MatchingRuleUseDescription = LPAREN WSP
458     *          numericoid                ; object identifier
459     *          [ SP &quot;NAME&quot; SP qdescrs ]  ; short names (descriptors)
460     *          [ SP &quot;DESC&quot; SP qdstring ] ; description
461     *          [ SP &quot;OBSOLETE&quot; ]         ; not active
462     *          SP &quot;APPLIES&quot; SP oids      ; attribute types
463     *          extensions WSP RPAREN     ; extensions
464     *  
465     *    where:
466     *      [numericoid] is the object identifier of the matching rule
467     *          associated with this matching rule use description;
468     *      NAME [qdescrs] are short names (descriptors) identifying this
469     *          matching rule use;
470     *      DESC [qdstring] is a short descriptive string;
471     *      OBSOLETE indicates this matching rule use is not active;
472     *      APPLIES provides a list of attribute types the matching rule applies
473     *          to; and
474     *      [extensions] describe extensions.
475     * </pre>
476     * 
477     * @param matchingRuleUse The matching rule from which we want to generate
478     *  a MatchingRuleUseDescription.
479     * @return The generated MatchingRuleUseDescription
480     */
481    public static String getDescription( MatchingRuleUse matchingRuleUse )
482    {
483        StringBuilder buf = new StringBuilder( "( " );
484        buf.append( matchingRuleUse.getOid() );
485        buf.append( '\n' );
486
487        buf.append( " NAME " );
488        getQDescrs( buf, matchingRuleUse.getNames() );
489
490        if ( matchingRuleUse.getDescription() != null )
491        {
492            buf.append( " DESC " );
493            buf.append( matchingRuleUse.getDescription() );
494            buf.append( '\n' );
495        }
496
497        if ( matchingRuleUse.isObsolete() )
498        {
499            buf.append( " OBSOLETE\n" );
500        }
501
502        buf.append( " APPLIES " );
503        List<AttributeType> attributeTypes = matchingRuleUse.getApplicableAttributes();
504
505        if ( attributeTypes.size() == 1 )
506        {
507            buf.append( attributeTypes.get( 0 ).getOid() );
508        }
509        else
510        // for list of oids we need a parenthesis
511        {
512            buf.append( "( " );
513
514            boolean isFirst = true;
515
516            for ( AttributeType attributeType : attributeTypes )
517            {
518                if ( isFirst )
519                {
520                    isFirst = false;
521                }
522                else
523                {
524                    buf.append( " $ " );
525                }
526
527                buf.append( attributeType );
528            }
529
530            buf.append( " ) " );
531        }
532
533        if ( matchingRuleUse.getExtensions() != null )
534        {
535            getExtensions( buf, matchingRuleUse.getExtensions() );
536        }
537
538        buf.append( " )\n" );
539
540        return buf.toString();
541    }
542
543
544    /**
545     * Generates the NameFormDescription for a NameForm as defined by the
546     * syntax: 1.3.6.1.4.1.1466.115.121.1.35. Only the right hand side of the
547     * description starting at the opening parenthesis is generated: that is
548     * 'NameFormDescription = ' is not generated.
549     * 
550     * <pre>
551     *  NameFormDescription = &quot;(&quot; whsp
552     *      numericoid whsp               ; NameForm identifier
553     *      [ &quot;NAME&quot; qdescrs ]
554     *      [ &quot;DESC&quot; qdstring ]
555     *      [ &quot;OBSOLETE&quot; whsp ]
556     *      &quot;OC&quot; woid                     ; Structural ObjectClass
557     *      &quot;MUST&quot; oids                   ; AttributeTypes
558     *      [ &quot;MAY&quot; oids ]                ; AttributeTypes
559     *  whsp &quot;)&quot;
560     * </pre>
561     * 
562     * @param nameForm
563     *            the NameForm to generate the description for
564     * @return the NameFormDescription string
565     */
566    public static String getDescription( NameForm nameForm )
567    {
568        StringBuilder buf = new StringBuilder( "( " );
569        buf.append( nameForm.getOid() );
570        buf.append( '\n' );
571
572        if ( nameForm.getNames() != null )
573        {
574            buf.append( " NAME " );
575            getQDescrs( buf, nameForm.getNames() );
576        }
577
578        if ( nameForm.getDescription() != null )
579        {
580            buf.append( " DESC " );
581            buf.append( nameForm.getDescription() );
582            buf.append( '\n' );
583        }
584
585        if ( nameForm.isObsolete() )
586        {
587            buf.append( " OBSOLETE\n" );
588        }
589
590        buf.append( " OC " );
591        buf.append( nameForm.getStructuralObjectClassOid() );
592        buf.append( '\n' );
593
594        buf.append( " MUST\n" );
595        List<AttributeType> must = nameForm.getMustAttributeTypes();
596
597        getQDStrings( buf, must );
598
599        List<AttributeType> may = nameForm.getMayAttributeTypes();
600
601        if ( ( may != null ) && ( may.size() > 0 ) )
602        {
603            buf.append( " MAY\n" );
604            getQDStrings( buf, may );
605        }
606
607        if ( nameForm.getExtensions() != null )
608        {
609            getExtensions( buf, nameForm.getExtensions() );
610        }
611
612        buf.append( " )\n" );
613        return buf.toString();
614    }
615
616
617    /**
618     * Generates the NormalizerDescription for a Normalizer. Only the right 
619     * hand side of the description starting at the opening parenthesis is 
620     * generated: that is 'NormalizerDescription = ' is not generated.
621     * 
622     * <pre>
623     * NormalizerDescription = &quot;(&quot;
624     *     numericoid                          
625     *     [&quot;DESC&quot; qdstring ]
626     *     &quot;FQCN&quot; whsp fqcn
627     *     [&quot;BYTECODE&quot; whsp base64  ]
628     *     extensions 
629     *     &quot;)&quot;
630     * </pre>
631     * 
632     * @param normalizer
633     *            the Normalizer to generate the description for
634     * @return the NormalizerDescription string
635     */
636    public static String getDescription( Normalizer normalizer )
637    {
638        return getLoadableDescription( normalizer );
639    }
640
641
642    /**
643     * Generates the ObjectClassDescription for an ObjectClass as defined by the
644     * syntax: 1.3.6.1.4.1.1466.115.121.1.37. Only the right hand side of the
645     * description starting at the opening parenthesis is generated: that is
646     * 'ObjectClassDescription = ' is not generated.
647     * 
648     * <pre>
649     *  ObjectClassDescription = &quot;(&quot; whsp
650     *      numericoid whsp     ; ObjectClass identifier
651     *      [ &quot;NAME&quot; qdescrs ]
652     *      [ &quot;DESC&quot; qdstring ]
653     *      [ &quot;OBSOLETE&quot; whsp ]
654     *      [ &quot;SUP&quot; oids ]      ; Superior ObjectClasses
655     *      [ ( &quot;ABSTRACT&quot; / &quot;STRUCTURAL&quot; / &quot;AUXILIARY&quot; ) whsp ]
656     *                          ; default structural
657     *      [ &quot;MUST&quot; oids ]     ; AttributeTypes
658     *      [ &quot;MAY&quot; oids ]      ; AttributeTypes
659     *  whsp &quot;)&quot;
660     * </pre>
661     * 
662     * @param objectClass
663     *            the ObjectClass to generate a description for
664     * @return the description in the ObjectClassDescription syntax
665     */
666    public static String getDescription( ObjectClass objectClass )
667    {
668        StringBuilder buf = new StringBuilder( "( " );
669        buf.append( objectClass.getOid() );
670        buf.append( '\n' );
671
672        if ( ( objectClass.getNames() != null ) && ( objectClass.getNames().size() != 0 ) )
673        {
674            buf.append( " NAME " );
675            getQDescrs( buf, objectClass.getNames() );
676        }
677
678        if ( objectClass.getDescription() != null )
679        {
680            buf.append( " DESC " );
681            buf.append( objectClass.getDescription() );
682            buf.append( '\n' );
683        }
684
685        if ( objectClass.isObsolete() )
686        {
687            buf.append( " OBSOLETE\n" );
688        }
689
690        List<ObjectClass> sups = objectClass.getSuperiors();
691
692        if ( ( sups != null ) && ( sups.size() > 0 ) )
693        {
694            buf.append( " SUP " );
695            getQDStrings( buf, sups );
696        }
697
698        if ( objectClass.getType() != null )
699        {
700            buf.append( ' ' );
701            buf.append( objectClass.getType() );
702            buf.append( '\n' );
703        }
704
705        List<AttributeType> must = objectClass.getMustAttributeTypes();
706
707        if ( ( must != null ) && ( must.size() > 0 ) )
708        {
709            buf.append( " MUST " );
710            getQDStrings( buf, must );
711        }
712
713        List<AttributeType> may = objectClass.getMayAttributeTypes();
714
715        if ( ( may != null ) && ( may.size() > 0 ) )
716        {
717            buf.append( " MAY " );
718            getQDStrings( buf, may );
719        }
720
721        if ( objectClass.getExtensions() != null )
722        {
723            getExtensions( buf, objectClass.getExtensions() );
724        }
725
726        buf.append( " )\n" );
727
728        return buf.toString();
729    }
730
731
732    /**
733     * Generates the SyntaxDescription for a Syntax as defined by the syntax:
734     * 1.3.6.1.4.1.1466.115.121.1.54. Only the right hand side of the
735     * description starting at the opening parenthesis is generated: that is
736     * 'SyntaxDescription = ' is not generated.
737     * 
738     * <pre>
739     *  SyntaxDescription = &quot;(&quot; whsp
740     *      numericoid whsp
741     *      [ &quot;DESC&quot; qdstring ]
742     *      [ extensions ]
743     *      whsp &quot;)&quot;
744     * </pre>
745     * 
746     * @param syntax
747     *            the Syntax to generate a description for
748     * @return the description in the SyntaxDescription syntax
749     */
750    public static String getDescription( LdapSyntax syntax )
751    {
752        StringBuilder buf = new StringBuilder( "( " );
753        buf.append( syntax.getOid() );
754        buf.append( '\n' );
755
756        if ( syntax.getDescription() != null )
757        {
758            buf.append( " DESC " );
759            buf.append( syntax.getDescription() );
760            buf.append( '\n' );
761        }
762
763        if ( syntax.getExtensions() != null )
764        {
765            getExtensions( buf, syntax.getExtensions() );
766        }
767
768        buf.append( " )" );
769        return buf.toString();
770    }
771
772
773    /**
774     * Generates the SyntaxCheckerDescription for a SyntaxChecker. Only the right 
775     * hand side of the description starting at the opening parenthesis is 
776     * generated: that is 'SyntaxCheckerDescription = ' is not generated.
777     * 
778     * <pre>
779     * SyntaxCheckerDescription = &quot;(&quot;
780     *     numericoid                          
781     *     [&quot;DESC&quot; qdstring ]
782     *     &quot;FQCN&quot; whsp fqcn
783     *     [&quot;BYTECODE&quot; whsp base64  ]
784     *     extensions 
785     *     &quot;)&quot;
786     * </pre>
787     * 
788     * @param syntaxChecker
789     *            the SyntaxChecker to generate the description for
790     * @return the SyntaxCheckerDescription string
791     */
792    public static String getDescription( SyntaxChecker syntaxChecker )
793    {
794        return getLoadableDescription( syntaxChecker );
795    }
796
797
798    private static void getExtensions( StringBuilder sb, Map<String, List<String>> extensions )
799    {
800        for ( Map.Entry<String, List<String>> extension : extensions.entrySet())
801        {
802            sb.append( " " + extension.getKey() ).append( " " );
803
804            List<String> values = extension.getValue();
805
806            if ( ( values != null ) && ( values.size() != 0 ) )
807            {
808                if ( values.size() == 1 )
809                {
810                    sb.append( values.get( 0 ) );
811                }
812                else
813                {
814                    boolean isFirst = true;
815                    sb.append( "( " );
816
817                    for ( String value : values )
818                    {
819                        if ( isFirst )
820                        {
821                            isFirst = false;
822                        }
823                        else
824                        {
825                            sb.append( " " );
826                        }
827
828                        sb.append( value );
829                    }
830
831                    sb.append( " )" );
832                }
833            }
834
835            sb.append( '\n' );
836        }
837    }
838
839
840    private static void getQDStrings( StringBuilder sb, List<? extends SchemaObject> schemaObjects )
841    {
842        if ( ( schemaObjects != null ) && ( schemaObjects.size() != 0 ) )
843        {
844            if ( schemaObjects.size() == 1 )
845            {
846                sb.append( '\'' ).append( schemaObjects.get( 0 ).getName() ).append( '\'' );
847            }
848            else
849            {
850                boolean isFirst = true;
851                sb.append( "( " );
852
853                for ( SchemaObject schemaObject : schemaObjects )
854                {
855                    if ( isFirst )
856                    {
857                        isFirst = false;
858                    }
859                    else
860                    {
861                        sb.append( " $ " );
862                    }
863
864                    sb.append( '\'' ).append( schemaObject.getName() ).append( '\'' );
865                }
866
867                sb.append( " )" );
868            }
869        }
870
871        sb.append( '\n' );
872    }
873
874
875    private static void getQDescrs( StringBuilder sb, List<String> names )
876    {
877        if ( ( names != null ) && ( names.size() != 0 ) )
878        {
879            if ( names.size() == 1 )
880            {
881                sb.append( '\'' ).append( names.get( 0 ) ).append( '\'' );
882            }
883            else
884            {
885                boolean isFirst = true;
886                sb.append( "( " );
887
888                for ( String name : names )
889                {
890                    if ( isFirst )
891                    {
892                        isFirst = false;
893                    }
894                    else
895                    {
896                        sb.append( " " );
897                    }
898
899                    sb.append( '\'' ).append( name ).append( '\'' );
900                }
901
902                sb.append( " )" );
903            }
904        }
905
906        sb.append( '\n' );
907    }
908
909
910    /**
911     * Generate the description for Comparators, Normalizers and SyntaxCheckers.
912     */
913    private static String getLoadableDescription( LoadableSchemaObject schemaObject )
914    {
915        StringBuilder buf = new StringBuilder( "( " );
916        buf.append( schemaObject.getOid() );
917        buf.append( '\n' );
918
919        if ( schemaObject.getDescription() != null )
920        {
921            buf.append( " DESC " );
922            buf.append( schemaObject.getDescription() );
923            buf.append( '\n' );
924        }
925
926        if ( schemaObject.getFqcn() != null )
927        {
928            buf.append( " FQCN " );
929            buf.append( schemaObject.getFqcn() );
930            buf.append( '\n' );
931        }
932
933        if ( schemaObject.getBytecode() != null )
934        {
935            buf.append( " BYTECODE " );
936
937            // We will dump only the 16 first bytes
938            if ( schemaObject.getBytecode().length() > 16 )
939            {
940                buf.append( schemaObject.getBytecode().substring( 0, 16 ) );
941            }
942            else
943            {
944                buf.append( schemaObject.getBytecode() );
945            }
946
947            buf.append( '\n' );
948        }
949
950        if ( schemaObject.getExtensions() != null )
951        {
952            getExtensions( buf, schemaObject.getExtensions() );
953        }
954
955        buf.append( " ) " );
956
957        return buf.toString();
958    }
959}