View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.api.ldap.model.schema;
21  
22  
23  import java.util.List;
24  
25  
26  /**
27   * Renderer for schema objects.
28   * 
29   * Currently the following preconfigured renderers exist: 
30   * <ol>
31   * <li> {@link SchemaObjectRenderer#SUBSCHEMA_SUBENTRY_RENDERER}: renders the schema object 
32   *      without line break and with X-SCHEMA extension. To be used for building subschema subentry.
33   * <li> {@link SchemaObjectRenderer#OPEN_LDAP_SCHEMA_RENDERER}: renders the schema object in OpenLDAP schema  
34   *      format. That means is starts with schema type and contains line breaks for easier readability.
35   * </ol>
36   * <p>
37   * TODO: currently only {@link ObjectClass} and {@link AttributeType} are supported, implement other schema object types.
38   * 
39   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
40   */
41  public final class SchemaObjectRenderer
42  {
43      /**
44       * Preconfigured {@link SchemaObjectRenderer} that renders the schema object without line break and with
45       * X-SCHEMA extension. To be used for building subschema subentry.
46       */
47      public static final SchemaObjectRenderer SUBSCHEMA_SUBENTRY_RENDERER = new SchemaObjectRenderer(
48          Style.SUBSCHEMA_SUBENTRY_WITH_SCHEMA_NAME );
49  
50      /**
51       * Preconfigured {@link SchemaObjectRenderer} that renders the schema object in OpenLDAP schema format. 
52       * That means is starts with schema type and contains line breaks for easier readability.
53       */
54      public static final SchemaObjectRenderer OPEN_LDAP_SCHEMA_RENDERER = new SchemaObjectRenderer(
55          Style.OPENLDAP_SCHEMA_PRETTY_PRINTED );
56  
57      private enum Style
58      {
59          SUBSCHEMA_SUBENTRY_WITH_SCHEMA_NAME(false, false, true),
60  
61          OPENLDAP_SCHEMA_PRETTY_PRINTED(true, true, false);
62  
63          final boolean startWithSchemaType;
64          final boolean prettyPrint;
65          final boolean printSchemaName;
66  
67  
68          Style( boolean startWithSchemaType, boolean prettyPrint, boolean printSchemaName )
69          {
70              this.startWithSchemaType = startWithSchemaType;
71              this.prettyPrint = prettyPrint;
72              this.printSchemaName = printSchemaName;
73          }
74      }
75  
76      private final Style style;
77  
78  
79      private SchemaObjectRenderer( Style style )
80      {
81          this.style = style;
82      }
83  
84  
85      /**
86       * Renders an objectClass according to the Object Class 
87       * Description Syntax 1.3.6.1.4.1.1466.115.121.1.37. The syntax is
88       * described in detail within section 4.1.1. of 
89       * <a href="https://tools.ietf.org/rfc/rfc4512.txt">RFC 4512</a>
90       * which is replicated here for convenience:
91       * 
92       * <pre>
93       *  4.1.1. Object Class Definitions
94       * 
95       *   Object Class definitions are written according to the ABNF:
96       * 
97       *     ObjectClassDescription = LPAREN WSP
98       *         numericoid                 ; object identifier
99       *         [ 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 }