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