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.api.ldap.model.schema;
021
022
023import java.util.List;
024
025
026/**
027 * Renderer for schema objects.
028 * 
029 * Currently the following preconfigured renderers exist: 
030 * <ol>
031 * <li> {@link SchemaObjectRenderer#SUBSCHEMA_SUBENTRY_RENDERER}: renders the schema object 
032 *      without line break and with X-SCHEMA extension. To be used for building subschema subentry.
033 * <li> {@link SchemaObjectRenderer#OPEN_LDAP_SCHEMA_RENDERER}: renders the schema object in OpenLDAP schema  
034 *      format. That means is starts with schema type and contains line breaks for easier readability.
035 * </ol>
036 * <p>
037 * TODO: currently only {@link ObjectClass} and {@link AttributeType} are supported, implement other schema object types.
038 * 
039 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
040 */
041public final class SchemaObjectRenderer
042{
043    /**
044     * Preconfigured {@link SchemaObjectRenderer} that renders the schema object without line break and with
045     * X-SCHEMA extension. To be used for building subschema subentry.
046     */
047    public static final SchemaObjectRenderer SUBSCHEMA_SUBENTRY_RENDERER = new SchemaObjectRenderer(
048        Style.SUBSCHEMA_SUBENTRY_WITH_SCHEMA_NAME );
049
050    /**
051     * Preconfigured {@link SchemaObjectRenderer} that renders the schema object in OpenLDAP schema format. 
052     * That means is starts with schema type and contains line breaks for easier readability.
053     */
054    public static final SchemaObjectRenderer OPEN_LDAP_SCHEMA_RENDERER = new SchemaObjectRenderer(
055        Style.OPENLDAP_SCHEMA_PRETTY_PRINTED );
056
057    private enum Style
058    {
059        SUBSCHEMA_SUBENTRY_WITH_SCHEMA_NAME(false, false, true),
060
061        OPENLDAP_SCHEMA_PRETTY_PRINTED(true, true, false);
062
063        final boolean startWithSchemaType;
064        final boolean prettyPrint;
065        final boolean printSchemaName;
066
067
068        Style( boolean startWithSchemaType, boolean prettyPrint, boolean printSchemaName )
069        {
070            this.startWithSchemaType = startWithSchemaType;
071            this.prettyPrint = prettyPrint;
072            this.printSchemaName = printSchemaName;
073        }
074    }
075
076    private final Style style;
077
078
079    private SchemaObjectRenderer( Style style )
080    {
081        this.style = style;
082    }
083
084
085    /**
086     * Renders an objectClass according to the Object Class 
087     * Description Syntax 1.3.6.1.4.1.1466.115.121.1.37. The syntax is
088     * described in detail within section 4.1.1. of 
089     * <a href="https://tools.ietf.org/rfc/rfc4512.txt">RFC 4512</a>
090     * which is replicated here for convenience:
091     * 
092     * <pre>
093     *  4.1.1. Object Class Definitions
094     * 
095     *   Object Class definitions are written according to the ABNF:
096     * 
097     *     ObjectClassDescription = LPAREN WSP
098     *         numericoid                 ; object identifier
099     *         [ SP &quot;NAME&quot; SP qdescrs ]   ; short names (descriptors)
100     *         [ SP &quot;DESC&quot; SP qdstring ]  ; description
101     *         [ SP &quot;OBSOLETE&quot; ]          ; not active
102     *         [ SP &quot;SUP&quot; SP oids ]       ; superior object classes
103     *         [ SP kind ]                ; kind of class
104     *         [ SP &quot;MUST&quot; SP oids ]      ; attribute types
105     *         [ SP &quot;MAY&quot; SP oids ]       ; attribute types
106     *         extensions WSP RPAREN
107     * 
108     *     kind = &quot;ABSTRACT&quot; / &quot;STRUCTURAL&quot; / &quot;AUXILIARY&quot;
109     * 
110     *   where:
111     *     &lt;numericoid&gt; is object identifier assigned to this object class;
112     *     NAME &lt;qdescrs&gt; are short names (descriptors) identifying this object
113     *         class;
114     *     DESC &lt;qdstring&gt; is a short descriptive string;
115     *     OBSOLETE indicates this object class is not active;
116     *     SUP &lt;oids&gt; specifies the direct superclasses of this object class;
117     *     the kind of object class is indicated by one of ABSTRACT,
118     *         STRUCTURAL, or AUXILIARY, default is STRUCTURAL;
119     *     MUST and MAY specify the sets of required and allowed attribute
120     *         types, respectively; and
121     *     &lt;extensions&gt; describe extensions.
122     * </pre>
123     * @param oc the ObjectClass to render the description of
124     * @return the string form of the Object Class description
125     */
126    public String render( ObjectClass oc )
127    {
128        StringBuilder buf = renderStartOidNamesDescObsolete( oc, "objectclass" );
129
130        renderOids( buf, "SUP", oc.getSuperiorOids() );
131
132        if ( oc.getType() != null )
133        {
134            prettyPrintIndent( buf );
135            buf.append( oc.getType() );
136            prettyPrintNewLine( buf );
137        }
138
139        renderOids( buf, "MUST", oc.getMustAttributeTypeOids() );
140
141        renderOids( buf, "MAY", oc.getMayAttributeTypeOids() );
142
143        renderXSchemaName( oc, buf );
144
145        // @todo extensions are not presently supported and skipped
146        // the extensions would go here before closing off the description
147
148        renderClose( buf );
149
150        return buf.toString();
151    }
152
153
154    /**
155     * Renders an attributeType according to the
156     * Attribute Type Description Syntax 1.3.6.1.4.1.1466.115.121.1.3. The
157     * syntax is described in detail within section 4.1.2. of 
158     * <a href="https://tools.ietf.org/rfc/rfc4512.txt">RFC 4512</a>
159     * which is replicated here for convenience:
160     * 
161     * <pre>
162     *  4.1.2. Attribute Types
163     * 
164     *   Attribute Type definitions are written according to the ABNF:
165     * 
166     *   AttributeTypeDescription = LPAREN WSP
167     *         numericoid                    ; object identifier
168     *         [ SP &quot;NAME&quot; SP qdescrs ]      ; short names (descriptors)
169     *         [ SP &quot;DESC&quot; SP qdstring ]     ; description
170     *         [ SP &quot;OBSOLETE&quot; ]             ; not active
171     *         [ SP &quot;SUP&quot; SP oid ]           ; supertype
172     *         [ SP &quot;EQUALITY&quot; SP oid ]      ; equality matching rule
173     *         [ SP &quot;ORDERING&quot; SP oid ]      ; ordering matching rule
174     *         [ SP &quot;SUBSTR&quot; SP oid ]        ; substrings matching rule
175     *         [ SP &quot;SYNTAX&quot; SP noidlen ]    ; value syntax
176     *         [ SP &quot;SINGLE-VALUE&quot; ]         ; single-value
177     *         [ SP &quot;COLLECTIVE&quot; ]           ; collective
178     *         [ SP &quot;NO-USER-MODIFICATION&quot; ] ; not user modifiable
179     *         [ SP &quot;USAGE&quot; SP usage ]       ; usage
180     *         extensions WSP RPAREN         ; extensions
181     * 
182     *     usage = &quot;userApplications&quot;     /  ; user
183     *             &quot;directoryOperation&quot;   /  ; directory operational
184     *             &quot;distributedOperation&quot; /  ; DSA-shared operational
185     *             &quot;dSAOperation&quot;            ; DSA-specific operational
186     * 
187     *   where:
188     *     &lt;numericoid&gt; is object identifier assigned to this attribute type;
189     *     NAME &lt;qdescrs&gt; are short names (descriptors) identifying this
190     *         attribute type;
191     *     DESC &lt;qdstring&gt; is a short descriptive string;
192     *     OBSOLETE indicates this attribute type is not active;
193     *     SUP oid specifies the direct supertype of this type;
194     *     EQUALITY, ORDERING, SUBSTR provide the oid of the equality,
195     *         ordering, and substrings matching rules, respectively;
196     *     SYNTAX identifies value syntax by object identifier and may suggest
197     *         a minimum upper bound;
198     *     SINGLE-VALUE indicates attributes of this type are restricted to a
199     *         single value;
200     *     COLLECTIVE indicates this attribute type is collective
201     *         [X.501][RFC3671];
202     *     NO-USER-MODIFICATION indicates this attribute type is not user
203     *         modifiable;
204     *     USAGE indicates the application of this attribute type; and
205     *     &lt;extensions&gt; describe extensions.
206     * </pre>
207     * @param at the AttributeType to render the description for
208     * @return the StringBuffer containing the rendered attributeType description
209     */
210    public String render( AttributeType at )
211    {
212        StringBuilder buf = renderStartOidNamesDescObsolete( at, "attributetype" );
213
214        /*
215         *  TODO: Check for getSuperior(), getEquality(), getOrdering(), and getSubstring() should not be necessary. 
216         *  The getXyzOid() methods should return a name but return a numeric OID currently.
217         */
218
219        if ( at.getSuperior() != null )
220        {
221            prettyPrintIndent( buf );
222            buf.append( "SUP " ).append( at.getSuperior().getName() );
223            prettyPrintNewLine( buf );
224        }
225        else if ( at.getSuperiorOid() != null )
226        {
227            prettyPrintIndent( buf );
228            buf.append( "SUP " ).append( at.getSuperiorOid() );
229            prettyPrintNewLine( buf );
230        }
231
232        if ( at.getEquality() != null )
233        {
234            prettyPrintIndent( buf );
235            buf.append( "EQUALITY " ).append( at.getEquality().getName() );
236            prettyPrintNewLine( buf );
237        }
238        else if ( at.getEqualityOid() != null )
239        {
240            prettyPrintIndent( buf );
241            buf.append( "EQUALITY " ).append( at.getEqualityOid() );
242            prettyPrintNewLine( buf );
243        }
244
245        if ( at.getOrdering() != null )
246        {
247            prettyPrintIndent( buf );
248            buf.append( "ORDERING " ).append( at.getOrdering().getName() );
249            prettyPrintNewLine( buf );
250        }
251        else if ( at.getOrderingOid() != null )
252        {
253            prettyPrintIndent( buf );
254            buf.append( "ORDERING " ).append( at.getOrderingOid() );
255            prettyPrintNewLine( buf );
256        }
257
258        if ( at.getSubstring() != null )
259        {
260            prettyPrintIndent( buf );
261            buf.append( "SUBSTR " ).append( at.getSubstring().getName() );
262            prettyPrintNewLine( buf );
263        }
264        else if ( at.getSubstringOid() != null )
265        {
266            prettyPrintIndent( buf );
267            buf.append( "SUBSTR " ).append( at.getSubstringOid() );
268            prettyPrintNewLine( buf );
269        }
270
271        if ( at.getSyntaxOid() != null )
272        {
273            prettyPrintIndent( buf );
274            buf.append( "SYNTAX " ).append( at.getSyntaxOid() );
275
276            if ( at.getSyntaxLength() > 0 )
277            {
278                buf.append( "{" ).append( at.getSyntaxLength() ).append( "}" );
279            }
280            prettyPrintNewLine( buf );
281        }
282
283        if ( at.isSingleValued() )
284        {
285            prettyPrintIndent( buf );
286            buf.append( "SINGLE-VALUE" );
287            prettyPrintNewLine( buf );
288        }
289
290        if ( at.isCollective() )
291        {
292            prettyPrintIndent( buf );
293            buf.append( "COLLECTIVE" );
294            prettyPrintNewLine( buf );
295        }
296
297        if ( !at.isUserModifiable() )
298        {
299            prettyPrintIndent( buf );
300            buf.append( "NO-USER-MODIFICATION" );
301            prettyPrintNewLine( buf );
302        }
303
304        if ( at.getUsage() != null )
305        {
306            prettyPrintIndent( buf );
307            buf.append( "USAGE " ).append( UsageEnum.render( at.getUsage() ) );
308            prettyPrintNewLine( buf );
309        }
310
311        renderXSchemaName( at, buf );
312
313        // @todo extensions are not presently supported and skipped
314        // the extensions would go here before closing off the description
315
316        renderClose( buf );
317
318        return buf.toString();
319    }
320
321
322    /**
323     * Renders an matchingRule according to the
324     * MatchingRule Description Syntax 1.3.6.1.4.1.1466.115.121.1.30. The syntax
325     * is described in detail within section 4.1.3. 
326     * <a href="https://tools.ietf.org/rfc/rfc4512.txt">RFC 4512</a>
327     * which is replicated here for convenience:
328     * 
329     * <pre>
330     *  4.1.3. Matching Rules
331     * 
332     *   Matching rules are used in performance of attribute value assertions,
333     *   such as in performance of a Compare operation.  They are also used in
334     *   evaluation of a Search filters, in determining which individual values
335     *   are be added or deleted during performance of a Modify operation, and
336     *   used in comparison of distinguished names.
337     * 
338     *   Each matching rule is identified by an object identifier (OID) and,
339     *   optionally, one or more short names (descriptors).
340     * 
341     *   Matching rule definitions are written according to the ABNF:
342     * 
343     *   MatchingRuleDescription = LPAREN WSP
344     *        numericoid                 ; object identifier
345     *         [ SP &quot;NAME&quot; SP qdescrs ]   ; short names (descriptors)
346     *         [ SP &quot;DESC&quot; SP qdstring ]  ; description
347     *         [ SP &quot;OBSOLETE&quot; ]          ; not active
348     *         SP &quot;SYNTAX&quot; SP numericoid  ; assertion syntax
349     *         extensions WSP RPAREN      ; extensions
350     * 
351     *   where:
352     *     &lt;numericoid&gt; is object identifier assigned to this matching rule;
353     *     NAME &lt;qdescrs&gt; are short names (descriptors) identifying this
354     *         matching rule;
355     *     DESC &lt;qdstring&gt; is a short descriptive string;
356     *     OBSOLETE indicates this matching rule is not active;
357     *     SYNTAX identifies the assertion syntax (the syntax of the assertion
358     *         value) by object identifier; and
359     *     &lt;extensions&gt; describe extensions.
360     * </pre>
361     * @param mr the MatchingRule to render the description for
362     * @return the StringBuffer containing the rendered matchingRule description
363     */
364    public String render( MatchingRule mr )
365    {
366        StringBuilder buf = renderStartOidNamesDescObsolete( mr, "matchingrule" );
367
368        prettyPrintIndent( buf );
369        buf.append( "SYNTAX " ).append( mr.getSyntaxOid() );
370        prettyPrintNewLine( buf );
371
372        renderXSchemaName( mr, buf );
373
374        // @todo extensions are not presently supported and skipped
375        // the extensions would go here before closing off the description
376
377        renderClose( buf );
378
379        return buf.toString();
380    }
381
382
383    /**
384     * Renders a Syntax according to the LDAP Syntax
385     * Description Syntax 1.3.6.1.4.1.1466.115.121.1.54. The syntax is described
386     * in detail within section 4.1.5. of 
387     * <a href="https://tools.ietf.org/rfc/rfc4512.txt">RFC 4512</a>
388     * which is replicated here for convenience:
389     * 
390     * <pre>
391     *  LDAP syntax definitions are written according to the ABNF:
392     * 
393     *   SyntaxDescription = LPAREN WSP
394     *       numericoid                 ; object identifier
395     *       [ SP &quot;DESC&quot; SP qdstring ]  ; description
396     *       extensions WSP RPAREN      ; extensions
397     * 
398     *  where:
399     *   &lt;numericoid&gt; is the object identifier assigned to this LDAP syntax;
400     *   DESC &lt;qdstring&gt; is a short descriptive string; and
401     *   &lt;extensions&gt; describe extensions.
402     * </pre>
403     * @param syntax the Syntax to render the description for
404     * @return the StringBuffer containing the rendered syntax description
405     */
406    public String render( LdapSyntax syntax )
407    {
408        StringBuilder buf = new StringBuilder();
409
410        if ( style.startWithSchemaType )
411        {
412            buf.append( "ldapsyntax " );
413        }
414
415        buf.append( "( " ).append( syntax.getOid() );
416        prettyPrintNewLine( buf );
417
418        renderDescription( syntax, buf );
419
420        renderXSchemaName( syntax, buf );
421
422        prettyPrintIndent( buf );
423        if ( syntax.isHumanReadable() )
424        {
425            buf.append( "X-NOT-HUMAN-READABLE 'false'" );
426        }
427        else
428        {
429            buf.append( "X-NOT-HUMAN-READABLE 'true'" );
430        }
431        prettyPrintNewLine( buf );
432
433        // @todo extensions are not presently supported and skipped
434        // the extensions would go here before closing off the description
435
436        renderClose( buf );
437
438        return buf.toString();
439    }
440
441
442    /**
443     * NOT FULLY IMPLEMENTED!
444     * Renders a MatchingRuleUse as a String
445     * 
446     * @param mru The MatchingRuleUse to render
447     * @return The MatchingRuleUse as a String
448     */
449    public String render( MatchingRuleUse mru )
450    {
451        StringBuilder buf = renderStartOidNamesDescObsolete( mru, "matchingruleuse" );
452
453        List<String> applies = mru.getApplicableAttributeOids();
454
455        if ( ( applies != null ) && !applies.isEmpty() )
456        {
457            prettyPrintIndent( buf );
458            buf.append( "APPLIES " );
459            renderOids( buf, applies );
460            prettyPrintNewLine( buf );
461        }
462
463        renderXSchemaName( mru, buf );
464
465        // @todo extensions are not presently supported and skipped
466        // the extensions would go here before closing off the description
467
468        renderClose( buf );
469
470        return buf.toString();
471    }
472
473
474    /**
475     * NOT FULLY IMPLEMENTED!
476     * Renders a DitContentRule as a String
477     * 
478     * @param dcr The DitContentRule to render
479     * @return The DitContentRule as a String
480     */
481    public String render( DitContentRule dcr )
482    {
483        StringBuilder buf = renderStartOidNamesDescObsolete( dcr, "ditcontentrule" );
484
485        renderOids( buf, "AUX", dcr.getAuxObjectClassOids() );
486
487        renderOids( buf, "MUST", dcr.getMustAttributeTypeOids() );
488
489        renderOids( buf, "MAY", dcr.getMayAttributeTypeOids() );
490
491        renderOids( buf, "NOT", dcr.getNotAttributeTypeOids() );
492
493        renderXSchemaName( dcr, buf );
494
495        // @todo extensions are not presently supported and skipped
496        // the extensions would go here before closing off the description
497
498        renderClose( buf );
499
500        return buf.toString();
501    }
502
503
504    /**
505     * NOT FULLY IMPLEMENTED!
506     * 
507     * @param dsr The DitStructureRule to render
508     * @return The DitStructureRule as a String
509     */
510    public String render( DitStructureRule dsr )
511    {
512        StringBuilder buf = new StringBuilder();
513
514        if ( style.startWithSchemaType )
515        {
516            buf.append( "ditstructurerule " );
517        }
518
519        buf.append( "( " ).append( dsr.getRuleId() );
520
521        renderNames( dsr, buf );
522
523        renderDescription( dsr, buf );
524
525        renderObsolete( dsr, buf );
526
527        prettyPrintIndent( buf );
528        buf.append( "FORM " ).append( dsr.getForm() );
529        prettyPrintNewLine( buf );
530
531        renderRuleIds( buf, dsr.getSuperRules() );
532
533        renderXSchemaName( dsr, buf );
534
535        // @todo extensions are not presently supported and skipped
536        // the extensions would go here before closing off the description
537
538        renderClose( buf );
539
540        return buf.toString();
541    }
542
543
544    /**
545     * NOT FULLY IMPLEMENTED!
546     * Render a NameForm as a String
547     * 
548     * @param nf The NameForm to render
549     * @return The rendered String
550     */
551    public String render( NameForm nf )
552    {
553        StringBuilder buf = renderStartOidNamesDescObsolete( nf, "nameform" );
554
555        prettyPrintIndent( buf );
556        buf.append( "OC " ).append( nf.getStructuralObjectClassOid() );
557        prettyPrintNewLine( buf );
558
559        renderOids( buf, "MUST", nf.getMustAttributeTypeOids() );
560
561        renderOids( buf, "MAY", nf.getMayAttributeTypeOids() );
562
563        renderXSchemaName( nf, buf );
564
565        renderClose( buf );
566
567        return buf.toString();
568    }
569
570
571    private StringBuilder renderStartOidNamesDescObsolete( SchemaObject so, String schemaObjectType )
572    {
573        StringBuilder buf = new StringBuilder();
574
575        if ( style.startWithSchemaType )
576        {
577            buf.append( schemaObjectType ).append( ' ' );
578        }
579
580        buf.append( "( " ).append( so.getOid() );
581
582        renderNames( so, buf );
583
584        renderDescription( so, buf );
585
586        renderObsolete( so, buf );
587        return buf;
588    }
589
590
591    private void renderNames( SchemaObject so, StringBuilder buf )
592    {
593        List<String> names = so.getNames();
594
595        if ( ( names != null ) && !names.isEmpty() )
596        {
597            buf.append( " NAME " );
598            renderQDescrs( buf, names );
599            prettyPrintNewLine( buf );
600        }
601        else
602        {
603            prettyPrintNewLine( buf );
604        }
605    }
606
607
608    private void renderDescription( SchemaObject so, StringBuilder buf )
609    {
610        if ( so.getDescription() != null )
611        {
612            prettyPrintIndent( buf );
613            buf.append( "DESC " );
614            renderQDString( buf, so.getDescription() );
615            prettyPrintNewLine( buf );
616        }
617    }
618
619
620    private void renderObsolete( SchemaObject so, StringBuilder buf )
621    {
622        if ( so.isObsolete() )
623        {
624            prettyPrintIndent( buf );
625            buf.append( "OBSOLETE" );
626            prettyPrintNewLine( buf );
627        }
628    }
629
630
631    private void prettyPrintNewLine( StringBuilder buf )
632    {
633        if ( style.prettyPrint )
634        {
635            buf.append( '\n' );
636        }
637        else
638        {
639            buf.append( " " );
640        }
641    }
642
643
644    private void prettyPrintIndent( StringBuilder buf )
645    {
646        if ( style.prettyPrint )
647        {
648            buf.append( "\t" );
649        }
650    }
651
652
653    /**
654     * Renders qdescrs into a new buffer.<br>
655     * <pre>
656     * descrs ::= qdescr | '(' WSP qdescrlist WSP ')'
657     * qdescrlist ::= [ qdescr ( SP qdescr )* ]
658     * qdescr     ::= SQUOTE descr SQUOTE
659     * </pre>
660     * @param qdescrs the quoted description strings to render
661     * @return the string buffer the qdescrs are rendered into
662     */
663    private StringBuilder renderQDescrs( StringBuilder buf, List<String> qdescrs )
664    {
665        if ( ( qdescrs == null ) || qdescrs.isEmpty() )
666        {
667            return buf;
668        }
669
670        if ( qdescrs.size() == 1 )
671        {
672            buf.append( '\'' ).append( qdescrs.get( 0 ) ).append( '\'' );
673        }
674        else
675        {
676            buf.append( "( " );
677
678            for ( String qdescr : qdescrs )
679            {
680                buf.append( '\'' ).append( qdescr ).append( "' " );
681            }
682
683            buf.append( ")" );
684        }
685
686        return buf;
687    }
688
689
690    private void renderOids( StringBuilder buf, String prefix, List<String> oids )
691    {
692        if ( ( oids != null ) && !oids.isEmpty() )
693        {
694            prettyPrintIndent( buf );
695            buf.append( prefix ).append( ' ' );
696            renderOids( buf, oids );
697            prettyPrintNewLine( buf );
698        }
699    }
700
701
702    /**
703     * Renders oids into a new buffer.<br>
704     * <pre>
705     * oids    ::= oid | '(' WSP oidlist WSP ')'
706     * oidlist ::= oid ( WSP '$' WSP oid )*
707     * </pre>
708     * 
709     * @param qdescrs the quoted description strings to render
710     * @return the string buffer the qdescrs are rendered into
711     */
712    private StringBuilder renderOids( StringBuilder buf, List<String> oids )
713    {
714        if ( oids.size() == 1 )
715        {
716            buf.append( oids.get( 0 ) );
717        }
718        else
719        {
720            buf.append( "( " );
721
722            boolean isFirst = true;
723
724            for ( String oid : oids )
725            {
726                if ( isFirst )
727                {
728                    isFirst = false;
729                }
730                else
731                {
732                    buf.append( " $ " );
733                }
734
735                buf.append( oid );
736            }
737
738            buf.append( " )" );
739        }
740
741        return buf;
742    }
743
744
745    /**
746     * Renders QDString into a new buffer.<br>
747     * 
748     * @param qdescrs the quoted description strings to render
749     * @return the string buffer the qdescrs are rendered into
750     */
751    private StringBuilder renderQDString( StringBuilder buf, String qdString )
752    {
753        buf.append( '\'' );
754
755        for ( char c : qdString.toCharArray() )
756        {
757            switch ( c )
758            {
759                case 0x27:
760                    buf.append( "\\27" );
761                    break;
762
763                case 0x5C:
764                    buf.append( "\\5C" );
765                    break;
766
767                default:
768                    buf.append( c );
769                    break;
770            }
771        }
772
773        buf.append( '\'' );
774
775        return buf;
776    }
777
778
779    private StringBuilder renderRuleIds( StringBuilder buf, List<Integer> ruleIds )
780    {
781        if ( ( ruleIds != null ) && !ruleIds.isEmpty() )
782        {
783            prettyPrintIndent( buf );
784            buf.append( "SUP " );
785
786            if ( ruleIds.size() == 1 )
787            {
788                buf.append( ruleIds.get( 0 ) );
789            }
790            else
791            {
792                buf.append( "( " );
793
794                boolean isFirst = true;
795
796                for ( Integer ruleId : ruleIds )
797                {
798                    if ( isFirst )
799                    {
800                        isFirst = false;
801                    }
802                    else
803                    {
804                        buf.append( " " );
805                    }
806
807                    buf.append( ruleId );
808                }
809
810                buf.append( " )" );
811            }
812
813            prettyPrintNewLine( buf );
814        }
815
816        return buf;
817    }
818
819
820    private void renderXSchemaName( SchemaObject oc, StringBuilder buf )
821    {
822        if ( style.printSchemaName )
823        {
824            prettyPrintIndent( buf );
825            buf.append( "X-SCHEMA '" );
826            buf.append( oc.getSchemaName() );
827            buf.append( "'" );
828            prettyPrintNewLine( buf );
829        }
830    }
831
832
833    private void renderClose( StringBuilder buf )
834    {
835        if ( ( style.prettyPrint ) &&  ( buf.charAt( buf.length() - 1 ) == '\n' ) )
836        {
837            buf.deleteCharAt( buf.length() - 1 );
838            buf.append( " " );
839        }
840    
841        buf.append( ")" );
842    }
843
844}