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   *    https://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.parsers;
21  
22  
23  import java.io.BufferedReader;
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.InputStreamReader;
28  import java.io.Reader;
29  import java.io.StringReader;
30  import java.nio.charset.Charset;
31  import java.nio.file.Files;
32  import java.nio.file.Paths;
33  import java.text.ParseException;
34  import java.util.ArrayList;
35  import java.util.HashMap;
36  import java.util.List;
37  import java.util.Map;
38  
39  import org.apache.directory.api.asn1.util.Oid;
40  import org.apache.directory.api.i18n.I18n;
41  import org.apache.directory.api.ldap.model.exception.LdapSchemaException;
42  import org.apache.directory.api.ldap.model.schema.AttributeType;
43  import org.apache.directory.api.ldap.model.schema.DitContentRule;
44  import org.apache.directory.api.ldap.model.schema.DitStructureRule;
45  import org.apache.directory.api.ldap.model.schema.LdapSyntax;
46  import org.apache.directory.api.ldap.model.schema.MatchingRule;
47  import org.apache.directory.api.ldap.model.schema.MatchingRuleUse;
48  import org.apache.directory.api.ldap.model.schema.ObjectClass;
49  import org.apache.directory.api.ldap.model.schema.NameForm;
50  import org.apache.directory.api.ldap.model.schema.ObjectClassTypeEnum;
51  import org.apache.directory.api.ldap.model.schema.SchemaObject;
52  import org.apache.directory.api.ldap.model.schema.UsageEnum;
53  import org.apache.directory.api.ldap.model.schema.syntaxCheckers.OpenLdapObjectIdentifierMacro;
54  import org.apache.directory.api.util.Strings;
55  import org.slf4j.Logger;
56  import org.slf4j.LoggerFactory;
57  
58  /**
59   * A reusable wrapper for hand parser OpenLDAP schema.
60   *
61   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
62   */
63  public class OpenLdapSchemaParser
64  {
65      /** The LoggerFactory used by this class */
66      protected static final Logger LOG = LoggerFactory.getLogger( OpenLdapSchemaParser.class );
67  
68      /** A flag used to tell the parser if it should be strict or not */
69      private boolean isQuirksModeEnabled = false;
70  
71      /** the number of the current line being parsed by the reader */
72      protected int lineNumber;
73  
74      /** The list of parsed schema descriptions */
75      private List<Object> schemaDescriptions = new ArrayList<>();
76  
77      /** The list of attribute type, initialized by splitParsedSchemaDescriptions() */
78      private List<AttributeType> attributeTypes;
79  
80      /** The list of object classes, initialized by splitParsedSchemaDescriptions()*/
81      private List<ObjectClass> objectClasses;
82  
83      /** The map of object identifier macros, initialized by splitParsedSchemaDescriptions()*/
84      private Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros = new HashMap<>();
85      
86      /** Some contant strings used in descriptions */
87      private static final String APPLIES_STR                 = "APPLIES";
88      private static final String ABSTRACT_STR                = "ABSTRACT";
89      private static final String AUX_STR                     = "AUX";
90      private static final String AUXILIARY_STR               = "AUXILIARY";
91      private static final String BYTECODE_STR                = "BYTECODE";
92      private static final String COLLECTIVE_STR              = "COLLECTIVE";
93      private static final String DESC_STR                    = "DESC";
94      private static final String EQUALITY_STR                = "EQUALITY";
95      private static final String FORM_STR                    = "FORM";
96      private static final String FQCN_STR                    = "FQCN";
97      private static final String MAY_STR                     = "MAY";
98      private static final String MUST_STR                    = "MUST";
99      private static final String NAME_STR                    = "NAME";
100     private static final String NO_USER_MODIFICATION_STR    = "NO-USER-MODIFICATION";
101     private static final String NOT_STR                     = "NOT";
102     private static final String OBSOLETE_STR                = "OBSOLETE";
103     private static final String OC_STR                      = "OC";
104     private static final String ORDERING_STR                = "ORDERING";
105     private static final String SINGLE_VALUE_STR            = "SINGLE-VALUE";
106     private static final String STRUCTURAL_STR              = "STRUCTURAL";
107     private static final String SUBSTR_STR                  = "SUBSTR";
108     private static final String SUP_STR                     = "SUP";
109     private static final String SYNTAX_STR                  = "SYNTAX";
110     private static final String USAGE_STR                   = "USAGE";
111     private static final String EXTENSION_PREFIX            = "X-";
112     
113     /** Usage */
114     private static final String DIRECTORY_OPERATION_STR     = "directoryOperation";
115     private static final String DISTRIBUTED_OPERATION_STR   = "distributedOperation";
116     private static final String DSA_OPERATION_STR           = "dSAOperation";
117     private static final String USER_APPLICATIONS_STR       = "userApplications";
118 
119     /** Tokens */
120     private static final char COLON         = ':';
121     private static final char DOLLAR        = '$';
122     private static final char DOT           = '.';
123     private static final char EQUAL         = '=';
124     private static final char ESCAPE        = '\\';
125     private static final char HYPHEN        = '-';
126     private static final char LBRACE        = '{';
127     private static final char LPAREN        = '(';
128     private static final char PLUS          = '+';
129     private static final char RBRACE        = '}';
130     private static final char RPAREN        = ')';
131     private static final char SEMI_COLON    = ';';
132     private static final char SHARP         = '#';
133     private static final char SLASH         = '/';
134     private static final char SQUOTE        = '\'';
135     private static final char UNDERSCORE    = '_';
136     private static final char DQUOTE        = '"';
137 
138 
139     /** Flag whether object identifier macros should be resolved. */
140     private boolean isResolveObjectIdentifierMacros;
141     
142     private static final boolean UN_QUOTED = false;
143     
144     /** Flag for strict or relaxed mode */
145     private static final boolean STRICT = false;
146     private static final boolean RELAXED = true;
147     
148     private class PosSchema
149     {
150         /** The line number in the file */
151         int lineNumber;
152         
153         /** The position in the current line */
154         int start;
155         
156         /** The line being processed */
157         String line;
158         
159         /**
160          * {@inheritDoc} 
161          */
162         @Override
163         public String toString()
164         {
165             if ( line == null )
166             {
167                 return "null";
168             }
169             else if ( line.length() < start )
170             {
171                 return "EOL";
172             }
173             else
174             {
175                 return line.substring( start ); 
176             }
177         }
178     }
179 
180 
181     private interface SchemaObjectElements
182     {
183         int getValue();
184     }
185 
186     
187     /**
188      * The list of AttributeTypeDescription elements that can be seen 
189      */
190     private enum AttributeTypeElements implements SchemaObjectElements
191     {
192         NAME(1),
193         DESC(2),
194         OBSOLETE(4),
195         SUP(8),
196         EQUALITY(16),
197         ORDERING(32),
198         SUBSTR(64),
199         SYNTAX(128),
200         SINGLE_VALUE(256),
201         COLLECTIVE(512),
202         NO_USER_MODIFICATION(1024),
203         USAGE(2048);
204         
205         private int value;
206         
207         AttributeTypeElements( int value )
208         {
209             this.value = value;
210         }
211         
212         
213         public int getValue()
214         {
215             return value;
216         }
217     }
218     
219     
220     /**
221      * The list of DitContentRuleDescription elements that can be seen 
222      */
223     private enum DitContentRuleElements implements SchemaObjectElements
224     {
225         NAME(1),
226         DESC(2),
227         OBSOLETE(4),
228         AUX(8),
229         MUST(16),
230         MAY(32),
231         NOT(64);
232         
233         private int value;
234         
235         DitContentRuleElements( int value )
236         {
237             this.value = value;
238         }
239         
240         
241         public int getValue()
242         {
243             return value;
244         }
245     }
246 
247 
248     /**
249      * The list of DitStructureRuleDescription elements that can be seen 
250      */
251     private enum DitStructureRuleElements implements SchemaObjectElements
252     {
253         NAME(1),
254         DESC(2),
255         OBSOLETE(4),
256         FORM(8),
257         SUP(16);
258         
259         private int value;
260         
261         DitStructureRuleElements( int value )
262         {
263             this.value = value;
264         }
265         
266         
267         public int getValue()
268         {
269             return value;
270         }
271     }
272 
273     
274     /**
275      * The list of LdapComparatorDescription elements that can be seen 
276      */
277     private enum LdapComparatorElements implements SchemaObjectElements
278     {
279         DESC(1),
280         FQCN(2),
281         BYTECODE(4);
282         
283         private int value;
284         
285         LdapComparatorElements( int value )
286         {
287             this.value = value;
288         }
289         
290         
291         public int getValue()
292         {
293             return value;
294         }
295     }
296 
297     
298     /**
299      * The list of LdapSyntaxDescription elements that can be seen 
300      */
301     private enum LdapSyntaxElements implements SchemaObjectElements
302     {
303         DESC(1);
304         
305         private int value;
306         
307         LdapSyntaxElements( int value )
308         {
309             this.value = value;
310         }
311         
312         
313         public int getValue()
314         {
315             return value;
316         }
317     }
318 
319 
320     /**
321      * The list of MatchingRuleDescription elements that can be seen 
322      */
323     private enum MatchingRuleElements implements SchemaObjectElements
324     {
325         NAME(1),
326         DESC(2),
327         OBSOLETE(4),
328         SYNTAX(8);
329         
330         private int value;
331         
332         MatchingRuleElements( int value )
333         {
334             this.value = value;
335         }
336         
337         
338         public int getValue()
339         {
340             return value;
341         }
342     }
343 
344     
345     /**
346      * The list of MatchingRuleUseDescription elements that can be seen 
347      */
348     private enum MatchingRuleUseElements implements SchemaObjectElements
349     {
350         NAME(1),
351         DESC(2),
352         OBSOLETE(4),
353         APPLIES(8);
354         
355         private int value;
356         
357         MatchingRuleUseElements( int value )
358         {
359             this.value = value;
360         }
361         
362         
363         public int getValue()
364         {
365             return value;
366         }
367     }
368 
369     
370     /**
371      * The list of NameFormDescription elements that can be seen 
372      */
373     private enum NameFormElements implements SchemaObjectElements
374     {
375         NAME(1),
376         DESC(2),
377         OBSOLETE(4),
378         OC(8),
379         MUST(16),
380         MAY(32);
381         
382         private int value;
383         
384         NameFormElements( int value )
385         {
386             this.value = value;
387         }
388         
389         
390         public int getValue()
391         {
392             return value;
393         }
394     }
395 
396 
397     /**
398      * The list of NormalizerDescription elements that can be seen 
399      */
400     private enum NormalizerElements implements SchemaObjectElements
401     {
402         DESC(1),
403         FQCN(2),
404         BYTECODE(4);
405         
406         private int value;
407         
408         NormalizerElements( int value )
409         {
410             this.value = value;
411         }
412         
413         
414         public int getValue()
415         {
416             return value;
417         }
418     }
419 
420 
421     /**
422      * The list of ObjectClassDescription elements that can be seen 
423      */
424     private enum ObjectClassElements implements SchemaObjectElements
425     {
426         NAME(1),
427         DESC(2),
428         OBSOLETE(4),
429         SUP(8),
430         MUST(16),
431         MAY(32),
432         ABSTRACT(64),
433         STRUCTURAL(64),
434         AUXILIARY(64);
435         
436         private int value;
437         
438         ObjectClassElements( int value )
439         {
440             this.value = value;
441         }
442         
443         
444         public int getValue()
445         {
446             return value;
447         }
448     }
449 
450 
451     /**
452      * The list of SyntaxCheckerDescription elements that can be seen 
453      */
454     private enum SyntaxCheckerElements implements SchemaObjectElements
455     {
456         DESC(1),
457         FQCN(2),
458         BYTECODE(4);
459         
460         private int value;
461         
462         SyntaxCheckerElements( int value )
463         {
464             this.value = value;
465         }
466         
467         
468         public int getValue()
469         {
470             return value;
471         }
472     }
473 
474 
475     /**
476      * Creates a reusable instance of an OpenLdapSchemaParser.
477      */
478     public OpenLdapSchemaParser()
479     {
480         isResolveObjectIdentifierMacros = true;
481         isQuirksModeEnabled = false;
482     }
483 
484 
485     /**
486      * Reset the parser
487      */
488     public void clear()
489     {
490         if ( attributeTypes != null )
491         {
492             attributeTypes.clear();
493         }
494         
495         if ( objectClasses != null )
496         {
497             objectClasses.clear();
498         }
499         
500         if ( schemaDescriptions != null )
501         {
502             schemaDescriptions.clear();
503         }
504     
505         if ( objectIdentifierMacros != null )
506         {
507             objectIdentifierMacros.clear();
508         }
509     }
510 
511 
512     /**
513      * Gets the attribute types.
514      * 
515      * @return the attribute types
516      */
517     public List<AttributeType> getAttributeTypes()
518     {
519         return attributeTypes;
520     }
521 
522 
523     /**
524      * Gets the object class types.
525      * 
526      * @return the object class types
527      */
528     public List<ObjectClass> getObjectClasses()
529     {
530         return objectClasses;
531     }
532 
533 
534     /**
535      * Gets the object identifier macros.
536      * 
537      * @return the object identifier macros
538      */
539     public Map<String, OpenLdapObjectIdentifierMacro> getObjectIdentifierMacros()
540     {
541         return objectIdentifierMacros;
542     }
543 
544 
545     /**
546      * Splits parsed schema descriptions and resolved
547      * object identifier macros.
548      * 
549      * @throws ParseException the parse exception
550      */
551     private void afterParse() throws ParseException
552     {
553         objectClasses = new ArrayList<>();
554         attributeTypes = new ArrayList<>();
555 
556         // split parsed schema descriptions
557         for ( Object obj : schemaDescriptions )
558         {
559             if ( obj instanceof OpenLdapObjectIdentifierMacro )
560             {
561                 OpenLdapObjectIdentifierMacro oid = ( OpenLdapObjectIdentifierMacro ) obj;
562                 objectIdentifierMacros.put( oid.getName(), oid );
563             }
564             else if ( obj instanceof AttributeType )
565             {
566                 AttributeType attributeType = ( AttributeType ) obj;
567 
568                 attributeTypes.add( attributeType );
569             }
570             else if ( obj instanceof ObjectClass )
571             {
572                 ObjectClass objectClass = ( ObjectClass ) obj;
573 
574                 objectClasses.add( objectClass );
575             }
576         }
577 
578         if ( isResolveObjectIdentifierMacros() )
579         {
580             // resolve object identifier macros
581             for ( OpenLdapObjectIdentifierMacro oid : objectIdentifierMacros.values() )
582             {
583                 resolveObjectIdentifierMacro( oid );
584             }
585 
586             // apply object identifier macros to object classes
587             for ( ObjectClass objectClass : objectClasses )
588             {
589                 objectClass.setOid( getResolveOid( objectClass.getOid() ) );
590             }
591 
592             // apply object identifier macros to attribute types
593             for ( AttributeType attributeType : attributeTypes )
594             {
595                 attributeType.setOid( getResolveOid( attributeType.getOid() ) );
596                 attributeType.setSyntaxOid( getResolveOid( attributeType.getSyntaxOid() ) );
597             }
598 
599         }
600     }
601 
602 
603     /**
604      * Return a complete OID from a macro followed by an OID.
605      * 
606      * @param oid The OID to find
607      * @return The extended OID
608      */
609     private String getResolveOid( String oid )
610     {
611         if ( oid != null && oid.indexOf( COLON ) != -1 )
612         {
613             // resolve OID
614             String[] nameAndSuffix = oid.split( ":" );
615             
616             if ( objectIdentifierMacros.containsKey( nameAndSuffix[0] ) )
617             {
618                 OpenLdapObjectIdentifierMacro macro = objectIdentifierMacros.get( nameAndSuffix[0] );
619                 
620                 return macro.getResolvedOid() + "." + nameAndSuffix[1];
621             }
622         }
623         
624         return oid;
625     }
626 
627 
628     /**
629      * Find the proper OID from a OID which may contain a macro
630      * 
631      * @param macro The element to resolve
632      * @throws ParseException If teh OID is invalid
633      */
634     private void resolveObjectIdentifierMacro( OpenLdapObjectIdentifierMacro macro ) throws ParseException
635     {
636         String rawOidOrNameSuffix = macro.getRawOidOrNameSuffix();
637 
638         if ( !macro.isResolved() )
639         {
640             if ( rawOidOrNameSuffix.indexOf( COLON ) != -1 )
641             {
642                 // resolve OID
643                 String[] nameAndSuffix = rawOidOrNameSuffix.split( ":" );
644                 
645                 if ( objectIdentifierMacros.containsKey( nameAndSuffix[0] ) )
646                 {
647                     OpenLdapObjectIdentifierMacro parentMacro = objectIdentifierMacros.get( nameAndSuffix[0] );
648                     resolveObjectIdentifierMacro( parentMacro );
649                     macro.setResolvedOid( parentMacro.getResolvedOid() + "." + nameAndSuffix[1] );
650                 }
651                 else
652                 {
653                     throw new ParseException( I18n.err( I18n.ERR_13726_NO_OBJECT_IDENTIFIER_MACRO, nameAndSuffix[0] ), 0 );
654                 }
655             }
656             else
657             {
658                 // no :suffix,
659                 if ( objectIdentifierMacros.containsKey( rawOidOrNameSuffix ) )
660                 {
661                     OpenLdapObjectIdentifierMacro parentMacro = objectIdentifierMacros.get( rawOidOrNameSuffix );
662                     resolveObjectIdentifierMacro( parentMacro );
663                     macro.setResolvedOid( parentMacro.getResolvedOid() );
664                 }
665                 else
666                 {
667                     macro.setResolvedOid( rawOidOrNameSuffix );
668                 }
669             }
670         }
671     }
672 
673 
674     /**
675      * Parses an OpenLDAP schemaObject element/object.
676      *
677      * @param schemaObject the String image of a complete schema object
678      * @return the schema object
679      * @throws ParseException If the schemaObject can't be parsed
680      */
681     public SchemaObject parse( String schemaObject ) throws ParseException
682     {
683         if ( ( schemaObject == null ) || Strings.isEmpty( schemaObject.trim() ) )
684         {
685             throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
686         }
687         
688         try ( Reader reader = new BufferedReader( new StringReader( schemaObject ) ) )
689         {
690             parse( reader );
691             afterParse();
692         }
693         catch ( IOException | LdapSchemaException e )
694         {
695             throw new ParseException( e.getMessage(), 0 );
696         }
697 
698         if ( !schemaDescriptions.isEmpty() )
699         {
700             for ( Object obj : schemaDescriptions )
701             {
702                 if ( obj instanceof SchemaObject )
703                 {
704                     return ( SchemaObject ) obj;
705                 }
706             }
707         }
708         
709         return null;
710     }
711 
712 
713     /**
714      * Parses a stream of OpenLDAP schemaObject elements/objects. Default charset is used.
715      *
716      * @param schemaIn a stream of schema objects
717      * @throws ParseException  If the schema can't be parsed
718      * @throws LdapSchemaException If there is an error in the schema
719      * @throws IOException If the stream can't be read
720      */
721     public void parse( InputStream schemaIn ) throws ParseException, LdapSchemaException, IOException
722     {
723         try ( InputStreamReader in = new InputStreamReader( schemaIn, Charset.defaultCharset() ) )
724         {
725             try ( Reader reader = new BufferedReader( in ) )
726             {
727                 parse( reader );
728                 afterParse();
729             }
730         }
731     }
732     
733     
734     /**
735      * 
736      * @param reader The stream reader
737      * @param pos The position in the Schema
738      * @param mandatory If the spaces are mandatory
739      * @throws IOException If the stream can't be read
740      * @throws LdapSchemaException If the schema is wrong
741      */
742     private static void skipWhites( Reader reader, PosSchema pos, boolean mandatory ) throws IOException, LdapSchemaException
743     {
744         boolean hasSpace = false;
745         
746         while ( true )
747         {
748             if ( isEmpty( pos ) )
749             {
750                 getLine( reader, pos );
751                 
752                 if ( pos.line == null )
753                 {
754                     return;
755                 }
756                 
757                 hasSpace = true;
758                 continue;
759             }
760             
761             if ( pos.line == null )
762             {
763                 throw new LdapSchemaException( I18n.err( I18n.ERR_13782_END_OF_FILE, pos.lineNumber, pos.start ) );
764             }
765             
766             while ( Character.isWhitespace( pos.line.charAt( pos.start ) ) )
767             {
768                 hasSpace = true;
769                 pos.start++;
770                 
771                 if ( isEmpty( pos ) )
772                 {
773                     getLine( reader, pos );
774 
775                     if ( pos.line == null )
776                     {
777                         return;
778                     }
779                 }
780             }
781             
782             if ( pos.line.charAt( pos.start ) == SHARP )
783             {
784                 getLine( reader, pos );
785 
786                 if ( pos.line == null )
787                 {
788                     return;
789                 }
790                 
791                 hasSpace = true;
792             }
793             else
794             {
795                 if ( mandatory && !hasSpace )
796                 {
797                     throw new LdapSchemaException( I18n.err( I18n.ERR_13783_SPACE_EXPECTED, pos.lineNumber, pos.start ) );
798                 }
799                 else
800                 {
801                     return;
802                 }
803             }
804         }
805     }
806     
807     
808     /**
809      * @param pos The position in the Schema
810      * @return <tt>true</tt> if this is a comment
811      */
812     private static boolean isComment( PosSchema pos )
813     {
814         if ( isEmpty( pos ) )
815         {
816             return true;
817         }
818         
819         return pos.line.charAt( pos.start ) == SHARP;
820     }
821     
822     
823     /**
824      * @param pos The position in the Schema
825      * @return <tt>true</tt> of the line is empty
826      */
827     private static boolean isEmpty( PosSchema pos )
828     {
829         return ( pos.line == null ) || ( pos.start >= pos.line.length() );
830     }
831     
832     
833     /**
834      * @param pos The position in the Schema
835      * @param text The text to find at the beginning of the line
836      * @return <tt>true</tt> if teh line starts with the given text
837      */
838     private static boolean startsWith( PosSchema pos, String text )
839     {
840         if ( ( pos.line == null ) || ( pos.line.length() - pos.start < text.length() ) )
841         {
842             return false;
843         }
844         
845         return text.equalsIgnoreCase( pos.line.substring( pos.start, pos.start + text.length() ) );
846     }
847     
848     
849     /**
850      * Check if the stream starts with a given char at a given position
851      * 
852      * @param reader The stream reader
853      * @param pos The position in the Schema
854      * @param c The char to check
855      * @return <tt>true</tT> if the stream starts with the given char at the given position
856      * @throws IOException If we can't read the stream
857      * @throws LdapSchemaException If we have no char to read
858      */
859     private static boolean startsWith( Reader reader, PosSchema pos, char c ) throws IOException, LdapSchemaException
860     {
861         return startsWith( reader, pos, c, UN_QUOTED );
862     }
863     
864 
865     /**
866      * Check if the stream at the given position starts with a given char
867      * 
868      * @param reader The stream reader
869      * @param pos The position in the Schema
870      * @param c The char to check
871      * @param quoted <tt>true</tt> if the char is quoted
872      * @return <tt>true</tt> if the stream starts with the given char at the given position
873      * @throws IOException If we can't read the stream
874      * @throws LdapSchemaException If we have no char to read
875      */
876     private static boolean startsWith( Reader reader, PosSchema pos, char c, boolean quoted ) throws IOException, LdapSchemaException
877     {
878         if ( ( pos.line == null ) || ( pos.line.length() - pos.start < 1 ) )
879         {
880             return false;
881         }
882         
883         if ( quoted )
884         {
885             // Don't read a new line when we are within quotes
886             return pos.line.charAt( pos.start ) == c;
887         }
888 
889         while ( isEmpty( pos ) || ( isComment( pos ) ) )
890         {
891             getLine( reader, pos );
892             
893             if ( pos.line == null )
894             {
895                 return false;
896             }
897             
898             skipWhites( reader, pos, false );
899             
900             if ( isComment( pos ) )
901             {
902                 continue;
903             }
904         }
905         
906         return pos.line.charAt( pos.start ) == c;
907     }
908     
909     
910     /**
911      * @param pos The position in the Schema
912      * @param c The char to find at the beginning of the line
913      * @return <tt>true</tt> if the char is found at the beginning of the line
914      */
915     private static boolean startsWith( PosSchema pos, char c )
916     {
917         if ( ( pos.line == null ) || ( pos.line.length() - pos.start < 1 ) )
918         {
919             return false;
920         }
921         
922         return pos.line.charAt( pos.start ) == c;
923     }
924 
925     
926     /**
927      * @param pos The position in the Schema
928      * @return <tt>true</tt> if the first char is alphabetic
929      */
930     private static boolean isAlpha( PosSchema pos )
931     {
932         return Character.isAlphabetic( pos.line.charAt( pos.start ) );
933     }
934     
935     
936     /**
937      * @param pos The position in the Schema
938      * @return <tt>true</tt> if the first char is a digit
939      */
940     private static boolean isDigit( PosSchema pos )
941     {
942         return Character.isDigit( pos.line.charAt( pos.start ) );
943     }
944 
945     
946     /**
947      * 
948      * @param reader The stream reader
949      * @param pos The position in the Schema
950      * @throws IOException If the stream can't be read
951      */
952     private static void getLine( Reader reader, PosSchema pos ) throws IOException
953     {
954         pos.line = ( ( BufferedReader ) reader ).readLine();
955         pos.start = 0;
956         
957         if ( pos.line != null )
958         {
959             pos.lineNumber++;
960         }
961     }
962     
963     
964     /**
965      * <pre>
966      * numericoid   ::= number ( DOT number )+
967      * number       ::= DIGIT | LDIGIT DIGIT+
968      * DIGIT        ::= %x30 | LDIGIT       ; "0"-"9"
969      * LDIGIT       ::= %x31-39             ; "1"-"9"
970      * DOT          ::= %x2E                ; period (".")
971      * </pre>
972      * 
973      * @param pos The position in the Schema
974      * @return The numeric OID
975      * @throws LdapSchemaException If the schema is wrong
976      */
977     private static String getNumericOid( PosSchema pos ) throws LdapSchemaException
978     {
979         int start = pos.start;
980         boolean isDot = false;
981         boolean isFirstZero = false;
982         boolean isFirstDigit = true; 
983         
984         while ( !isEmpty( pos ) )
985         {
986             char c = pos.line.charAt( pos.start );
987             
988             if ( Character.isDigit( c ) )
989             {
990                 if ( isFirstZero )
991                 {
992                     throw new LdapSchemaException( I18n.err( I18n.ERR_13784_BAD_OID_TWO_ZEROES, pos.lineNumber, pos.start ) );
993                 }
994                     
995                 if ( ( pos.line.charAt( pos.start ) == '0' ) && isFirstDigit )
996                 {
997                     isFirstZero = true;
998                 }
999                 
1000                 isDot = false;
1001                 pos.start++;
1002                 isFirstDigit = false;
1003             }
1004             else if ( c == DOT )
1005             {
1006                 if ( isDot )
1007                 {
1008                     // We can't have two consecutive dots or a dot at the beginning
1009                     throw new LdapSchemaException( I18n.err( I18n.ERR_13785_BAD_OID_CONSECUTIVE_DOTS, pos.lineNumber, pos.start ) );
1010                 }
1011                 
1012                 isFirstZero = false;
1013                 isFirstDigit = true;
1014                 pos.start++;
1015                 isDot = true;
1016             }
1017             else
1018             {
1019                 break;
1020             }
1021         }
1022         
1023         if ( isDot )
1024         {
1025             // We can't have two consecutive dots or a dot at the beginning
1026             throw new LdapSchemaException( I18n.err( I18n.ERR_13786_BAD_OID_DOT_AT_THE_END, pos.lineNumber, pos.start ) );
1027         }
1028 
1029         String oidStr = pos.line.substring( start, pos.start );
1030 
1031         if ( Oid.isOid( oidStr ) )
1032         {
1033             return oidStr;
1034         }
1035         else
1036         {
1037             throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.line, pos.start ) );
1038         }
1039     }
1040     
1041     
1042     /**
1043      * <pre>
1044      * partialNumericoid   ::= number ( DOT number )*
1045      * number              ::= DIGIT | LDIGIT DIGIT+
1046      * DIGIT               ::= %x30 | LDIGIT       ; "0"-"9"
1047      * LDIGIT              ::= %x31-39             ; "1"-"9"
1048      * DOT                 ::= %x2E                ; period (".")
1049      * </pre>
1050      * 
1051      * @param pos The position in the Schema
1052      * @return The found OID
1053      * @throws LdapSchemaException If the schema is wrong
1054      */
1055     private static String getPartialNumericOid( PosSchema pos ) throws LdapSchemaException
1056     {
1057         int start = pos.start;
1058         boolean isDot = false;
1059         boolean isFirstZero = false;
1060         boolean isFirstDigit = true; 
1061         
1062         while ( !isEmpty( pos ) )
1063         {
1064             if ( isDigit( pos ) )
1065             {
1066                 if ( isFirstZero )
1067                 {
1068                     throw new LdapSchemaException( I18n.err( I18n.ERR_13784_BAD_OID_TWO_ZEROES, pos.lineNumber, pos.start ) );
1069                 }
1070                     
1071                 if ( ( pos.line.charAt( pos.start ) == '0' ) && isFirstDigit )
1072                 {
1073                     isFirstZero = true;
1074                 }
1075                 
1076                 isDot = false;
1077                 pos.start++;
1078                 isFirstDigit = false;
1079             }
1080             else if ( startsWith( pos, DOT ) )
1081             {
1082                 if ( isDot )
1083                 {
1084                     // We can't have two consecutive dots or a dot at the beginning
1085                     throw new LdapSchemaException( I18n.err( I18n.ERR_13785_BAD_OID_CONSECUTIVE_DOTS, pos.lineNumber, pos.start ) );
1086                 }
1087                 
1088                 isFirstZero = false;
1089                 isFirstDigit = true;
1090                 pos.start++;
1091                 isDot = true;
1092             }
1093             else
1094             {
1095                 break;
1096             }
1097         }
1098         
1099         if ( isDot )
1100         {
1101             // We can't have two consecutive dots or a dot at the beginning
1102             throw new LdapSchemaException( I18n.err( I18n.ERR_13786_BAD_OID_DOT_AT_THE_END, pos.lineNumber, pos.start ) );
1103         }
1104 
1105         return pos.line.substring( start, pos.start );
1106     }
1107 
1108     
1109 
1110     
1111     /**
1112      * In relaxed mode :
1113      * <pre>
1114      * oid          ::= descr | numericoid
1115      * descr        ::= descrQ (COLON numericoid)
1116      * descrQ       ::= keystringQ
1117      * keystringQ   ::= LkeycharQ keycharQ*
1118      * LkeycharQ    ::= ALPHA | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP 
1119      * keycharQ     ::= ALPHA | DIGIT | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP 
1120      * numericoid   ::= number ( DOT number )+
1121      * number       ::= DIGIT | LDIGIT DIGIT+
1122      * ALPHA        ::= %x41-5A | %x61-7A   ; "A"-"Z" / "a"-"z"
1123      * DIGIT        ::= %x30 | LDIGIT       ; "0"-"9"
1124      * LDIGIT       ::= %x31-39             ; "1"-"9"
1125      * HYPHEN       ::= %x2D                ; hyphen ("-")
1126      * UNDERSCORE   ::= %x5F                ; underscore ("_")
1127      * DOT          ::= %x2E                ; period (".")
1128      * COLON        ::= %x3A                ; colon (":")
1129      * SEMI_COLON   ::= %x3B                ; semi-colon(";")
1130      * SHARP        ::= %x23                ; octothorpe (or sharp sign) ("#")
1131      * </pre>
1132      * 
1133      * @param pos The position in the Schema
1134      * @param objectIdentifierMacros The set of existing Macros
1135      * @return The found OID
1136      * @throws LdapSchemaException If the schema is wrong
1137      */
1138     private static String getOidAndMacroRelaxed( PosSchema pos, 
1139         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws LdapSchemaException
1140     {
1141         if ( isEmpty( pos ) )
1142         {
1143             return "";
1144         }
1145 
1146         // This is a OID name
1147         int start = pos.start;
1148         char c = pos.line.charAt( pos.start );
1149         boolean isDigit = Character.isDigit( c );
1150         
1151         while ( isDigit || Character.isAlphabetic( c ) || ( c == HYPHEN ) || ( c == UNDERSCORE )
1152             || ( c == SEMI_COLON ) || ( c == DOT ) || ( c == SHARP ) )
1153         {
1154             pos.start++;
1155             
1156             if ( isEmpty( pos ) )
1157             {
1158                 break;
1159             }
1160             
1161             c = pos.line.charAt( pos.start );
1162             isDigit = Character.isDigit( c );
1163         }
1164         
1165         String oidName = pos.line.substring( start, pos.start  );
1166         
1167         if ( Strings.isEmpty( oidName ) )
1168         {
1169             throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
1170         }
1171         
1172         // We may have a ':' followed by an OID
1173         if ( startsWith( pos, COLON ) )
1174         {
1175             pos.start++;
1176             String oid = getPartialNumericOid( pos );
1177             
1178             return objectIdentifierMacros.get( oidName ).getRawOidOrNameSuffix() + DOT + oid;
1179         }
1180         else
1181         {
1182             // Ok, we may just have an oidName
1183             OpenLdapObjectIdentifierMacro macro = objectIdentifierMacros.get( oidName );
1184             
1185             if ( macro == null )
1186             {
1187                 return oidName;
1188             }
1189             else
1190             {
1191                 return macro.getRawOidOrNameSuffix();
1192             }
1193         }
1194     }
1195 
1196     
1197     /**
1198      * In normal mode :
1199      * <pre>
1200      * oid          ::= descr | numericoid
1201      * descr        ::= keystring
1202      * keystring    ::= leadkeychar keychar*
1203      * leadkeychar  ::= ALPHA
1204      * keychar      ::= ALPHA | DIGIT | HYPHEN
1205      * numericoid   ::= number ( DOT number )+ |
1206      * number       ::= DIGIT | LDIGIT DIGIT+
1207      * ALPHA        ::= %x41-5A | %x61-7A   ; "A"-"Z" / "a"-"z"
1208      * DIGIT        ::= %x30 | LDIGIT       ; "0"-"9"
1209      * LDIGIT       ::= %x31-39             ; "1"-"9"
1210      * DOT          ::= %x2E                ; period (".")
1211      * HYPHEN       ::= %x2D                ; hyphen ("-")
1212      * </pre>
1213      * 
1214      * @param pos The position in the Schema
1215      * @return The found OID
1216      * @throws LdapSchemaException If the schema is wrong
1217      */
1218     private static String getOidStrict( PosSchema pos ) throws LdapSchemaException
1219     {
1220         if ( isEmpty( pos ) )
1221         {
1222             return "";
1223         }
1224 
1225         if ( isAlpha( pos ) )
1226         {
1227             // A descr
1228             return getDescrStrict( pos );
1229         }
1230         else if ( isDigit( pos ) )
1231         {
1232             // This is a numeric oid
1233             return getNumericOid( pos );
1234         }
1235         else
1236         {
1237             // This is an error
1238             throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
1239         }
1240     }
1241 
1242     
1243     /**
1244      * In quirks mode :
1245      * <pre>
1246      * oid          ::= descr-relaxed | numericoid | SQUOTE descr-relaxed SQUOTE |
1247      *                  DQUOTE descr-relaxed DQUOTE | SQUOTE numericoid SQUOTE |
1248      *                  DQUOTE numericoid DQUOTE
1249      * descr-relaxed::= macro (COLON numericoid)
1250      * macro        ::= keystring
1251      * keystring    ::= Lkeychar  keychar*
1252      * Lkeychar     ::= ALPHA | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP 
1253      * keychar      ::= ALPHA | DIGIT | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP 
1254      * numericoid   ::= number ( DOT number )+
1255      * number       ::= DIGIT | LDIGIT DIGIT+
1256      * ALPHA        ::= %x41-5A | %x61-7A   ; "A"-"Z" / "a"-"z"
1257      * DIGIT        ::= %x30 | LDIGIT       ; "0"-"9"
1258      * LDIGIT       ::= %x31-39             ; "1"-"9"
1259      * HYPHEN       ::= %x2D                ; hyphen ("-")
1260      * UNDERSCORE   ::= %x5F                ; underscore ("_")
1261      * DOT          ::= %x2E                ; period (".")
1262      * COLON        ::= %x3A                ; colon (":")
1263      * SEMI_COLON   ::= %x3B                ; semi-colon(";")
1264      * SHARP        ::= %x23                ; octothorpe (or sharp sign) ("#")
1265      * </pre>
1266      * 
1267      * @param pos The position in the Schema
1268      * @param hadQuote If we have had a quote
1269      * @return the found OID
1270      * @throws LdapSchemaException If the schema is wrong
1271      */
1272     private static String getOidRelaxed( PosSchema pos, boolean hadQuote ) throws LdapSchemaException
1273     {
1274         if ( isEmpty( pos ) )
1275         {
1276             return "";
1277         }
1278         
1279         boolean hasQuote = false;
1280 
1281         char c = pos.line.charAt( pos.start );
1282         
1283         if ( c == SQUOTE )
1284         {
1285             if ( hadQuote )
1286             {
1287                 return "";
1288             }
1289             
1290             hasQuote = true;
1291             pos.start++;
1292 
1293             if ( isEmpty( pos ) )
1294             {
1295                 return "";
1296             }
1297             
1298             c = pos.line.charAt( pos.start );
1299         }
1300         
1301         String oid;
1302 
1303         if ( Character.isAlphabetic( c ) )
1304         {
1305             // This is a OID name
1306             oid = getDescrRelaxed( pos );
1307         }
1308         else if ( Character.isDigit( c ) )
1309         {
1310             // This is a numeric oid
1311             oid = getNumericOid( pos );
1312         }
1313         else
1314         {
1315             // This is an error
1316             throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, 
1317                 pos.lineNumber, pos.start ) );
1318         }
1319         
1320         if ( isEmpty( pos ) )
1321         {
1322             if ( hasQuote || hadQuote )
1323             {
1324                 throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 
1325                     pos.lineNumber, pos.start ) );
1326             }
1327             else
1328             {
1329                 return oid;
1330             }
1331         }
1332         
1333         c = pos.line.charAt( pos.start );
1334         
1335         if ( ( c == SQUOTE ) && !hadQuote )
1336         {
1337            if ( hasQuote )
1338            {
1339                pos.start++;
1340            }
1341            else
1342            {
1343                throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 
1344                    pos.lineNumber, pos.start ) );
1345            }
1346         }
1347         
1348         return oid;
1349     }
1350     
1351     
1352     /**
1353      * In strict mode :
1354      * 
1355      * <pre>
1356      * descr        ::= keystring
1357      * keystring    ::= leadkeychar keychar*
1358      * leadkeychar  ::= ALPHA
1359      * keychar      ::= ALPHA | DIGIT | HYPHEN
1360      * numericoid   ::= number ( DOT number )+ |
1361      * number       ::= DIGIT | LDIGIT DIGIT+
1362      * ALPHA        ::= %x41-5A | %x61-7A   ; "A"-"Z" / "a"-"z"
1363      * DIGIT        ::= %x30 | LDIGIT       ; "0"-"9"
1364      * LDIGIT       ::= %x31-39             ; "1"-"9"
1365      * DOT          ::= %x2E                ; period (".")
1366      * HYPHEN       ::= %x2D                ; hyphen ("-")
1367      * </pre>
1368      * 
1369      * @param pos The position in the Schema
1370      * @return The descr
1371      * @throws LdapSchemaException If the schema is wrong
1372      */
1373     private static String getDescrStrict( PosSchema pos ) throws LdapSchemaException
1374     {
1375         int start = pos.start;
1376         boolean isFirst = true;
1377         
1378         while ( !isEmpty( pos ) )
1379         {
1380             if ( isFirst )
1381             {
1382                 isFirst = false;
1383                 
1384                 if ( isAlpha( pos ) ) 
1385                 {
1386                     // leadkeychar
1387                     pos.start++;
1388                 }
1389                 else
1390                 {
1391                     // Error, we are expecting a leadKeychar
1392                     throw new LdapSchemaException( I18n.err( I18n.ERR_13788_LEAD_KEY_CHAR_EXPECTED, 
1393                         pos.lineNumber, pos.start ) );
1394                 }
1395             }
1396             else
1397             {
1398                 char c = pos.line.charAt( pos.start );
1399                 
1400                 if ( Character.isAlphabetic( c ) || Character.isDigit( c ) || ( c == HYPHEN ) )
1401                 {
1402                     pos.start++;
1403                 }
1404                 else
1405                 {
1406                     // We are done 
1407                     return pos.line.substring( start, pos.start );
1408                 }
1409             }
1410         }
1411 
1412         return pos.line.substring( start, pos.start );
1413     }
1414     
1415     
1416     
1417     /**
1418      * In quirksMode :
1419      * 
1420      * <pre>
1421      * descr        ::= descrQ (COLON numericoid)
1422      * descrQ       ::= keystringQ
1423      * keystringQ   ::= LkeycharQ keycharQ*
1424      * LkeycharQ    ::= ALPHA | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP 
1425      * keycharQ     ::= ALPHA | DIGIT | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP 
1426      * numericoid   ::= number ( DOT number )+
1427      * number       ::= DIGIT | LDIGIT DIGIT+
1428      * ALPHA        ::= %x41-5A | %x61-7A   ; "A"-"Z" / "a"-"z"
1429      * DIGIT        ::= %x30 | LDIGIT       ; "0"-"9"
1430      * LDIGIT       ::= %x31-39             ; "1"-"9"
1431      * HYPHEN       ::= %x2D                ; hyphen ("-")
1432      * UNDERSCORE   ::= %x5F                ; underscore ("_")
1433      * DOT          ::= %x2E                ; period (".")
1434      * COLON        ::= %x3A                ; colon (":")
1435      * SEMI_COLON   ::= %x3B                ; semi-colon(";")
1436      * SHARP        ::= %x23                ; octothorpe (or sharp sign) ("#")
1437      * </pre>
1438      * 
1439      * @param pos The position in the Schema
1440      * @return The descr
1441      * @throws LdapSchemaException If the schema is wrong
1442      */
1443     private static String getDescrRelaxed( PosSchema pos ) throws LdapSchemaException
1444     {
1445         int start = pos.start;
1446         boolean isFirst = true;
1447         
1448         while ( !isEmpty( pos ) )
1449         {
1450             if ( isFirst )
1451             {
1452                 isFirst = false;
1453                 
1454                 char c = pos.line.charAt( pos.start );
1455                 
1456                 if ( Character.isAlphabetic( c ) || ( c == HYPHEN ) || ( c == UNDERSCORE )
1457                     || ( c == SEMI_COLON ) || ( c == DOT ) || ( c == COLON ) || ( c == SHARP ) ) 
1458                 {
1459                     // leadkeycharQ
1460                     pos.start++;
1461                 }
1462                 else
1463                 {
1464                     // Error, we are expecting a leadKeychar
1465                     throw new LdapSchemaException( I18n.err( I18n.ERR_13788_LEAD_KEY_CHAR_EXPECTED, 
1466                         pos.lineNumber, pos.start ) );
1467                 }
1468             }
1469             else
1470             {
1471                 char c = pos.line.charAt( pos.start );
1472                 
1473                 if ( Character.isAlphabetic( c ) || Character.isDigit( c ) || ( c == HYPHEN )
1474                     || ( c == UNDERSCORE ) || ( c == SEMI_COLON ) || ( c == DOT ) || ( c == COLON ) || ( c == SHARP ) ) 
1475                 {
1476                     pos.start++;
1477                 }
1478                 else
1479                 {
1480                     // We are done 
1481                     return pos.line.substring( start, pos.start );
1482                 }
1483             }
1484         }
1485         
1486         return pos.line.substring( start, pos.start );
1487     }
1488     
1489     
1490     /**
1491      * 
1492      * @param pos The position in the Schema
1493      * @return The found macro, if any
1494      * @throws LdapSchemaException If the schema is wrong
1495      */
1496     private String getMacro( PosSchema pos ) throws LdapSchemaException
1497     {
1498         if ( isQuirksModeEnabled )
1499         {
1500             int start = pos.start;
1501             boolean isFirst = true;
1502             
1503             while ( !isEmpty( pos ) )
1504             {
1505                 if ( isFirst )
1506                 {
1507                     isFirst = false;
1508                     
1509                     char c = pos.line.charAt( pos.start );
1510                     
1511                     if ( Character.isAlphabetic( c ) || ( c == HYPHEN ) || ( c == UNDERSCORE ) 
1512                         || ( c == SEMI_COLON ) || ( c == DOT ) || ( c == SHARP ) ) 
1513                     {
1514                         // leadkeycharQ
1515                         pos.start++;
1516                     }
1517                     else
1518                     {
1519                         // Error, we are expecting a leadKeychar
1520                         throw new LdapSchemaException( I18n.err( I18n.ERR_13788_LEAD_KEY_CHAR_EXPECTED, 
1521                             pos.lineNumber, pos.start ) );
1522                     }
1523                 }
1524                 else
1525                 {
1526                     char c = pos.line.charAt( pos.start );
1527                     
1528                     if ( Character.isAlphabetic( c ) || Character.isDigit( c ) || ( c == HYPHEN ) 
1529                         || ( c == UNDERSCORE ) || ( c == SEMI_COLON ) || ( c == DOT ) || ( c == SHARP ) ) 
1530                     {
1531                         pos.start++;
1532                     }
1533                     else
1534                     {
1535                         // We are done 
1536                         return pos.line.substring( start, pos.start );
1537                     }
1538                 }
1539             }
1540             
1541             return pos.line.substring( start, pos.start );
1542         }
1543         else
1544         {
1545             int start = pos.start;
1546             boolean isFirst = true;
1547             
1548             while ( !isEmpty( pos ) )
1549             {
1550                 if ( isFirst )
1551                 {
1552                     isFirst = false;
1553                     
1554                     if ( isAlpha( pos ) ) 
1555                     {
1556                         // leadkeychar
1557                         pos.start++;
1558                     }
1559                     else
1560                     {
1561                         // Error, we are expecting a leadKeychar
1562                         throw new LdapSchemaException( I18n.err( I18n.ERR_13788_LEAD_KEY_CHAR_EXPECTED, 
1563                             pos.lineNumber, pos.start ) );
1564                     }
1565                 }
1566                 else
1567                 {
1568                     char c = pos.line.charAt( pos.start );
1569                     
1570                     if ( Character.isAlphabetic( c ) || Character.isDigit( c ) || ( c == HYPHEN ) )
1571                     {
1572                         pos.start++;
1573                     }
1574                     else
1575                     {
1576                         // We are done 
1577                         return pos.line.substring( start, pos.start );
1578                     }
1579                 }
1580             }
1581 
1582             return pos.line.substring( start, pos.start );
1583         }
1584     }
1585     
1586     
1587     /**
1588      * <pre>
1589      * qdescr ::== SQUOTE descr SQUOTE
1590      * descr ::= keystring
1591      * keystring ::= leadkeychar *keychar
1592      * leadkeychar ::= ALPHA
1593      * keychar ::= ALPHA | DIGIT | HYPHEN
1594      * </pre>
1595      * 
1596      * In quirksMode :
1597      * 
1598      * <pre>
1599      * qdescr ::== SQUOTE descr SQUOTE | descr | SQUOTE numericoid SQUOTE
1600      * descr ::= keystring
1601      * keystring ::= keychar+
1602      * keychar ::= ALPHA | DIGIT | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP 
1603      * </pre>
1604      * 
1605      * @param reader The stream reader
1606      * @param pos The position in the Schema
1607      * @return The QDescr
1608      * @throws LdapSchemaException If the schema is wrong
1609      * @throws IOException If the stream can't be read
1610      */
1611     private static String getQDescrStrict( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
1612     {
1613         // The first quote
1614         if ( !startsWith( reader, pos, SQUOTE ) )
1615         {
1616             throw new LdapSchemaException( I18n.err( I18n.ERR_13789_SIMPLE_QUOTE_EXPECTED_AT_START, 
1617                 pos.lineNumber, pos.start ) );
1618         }
1619         
1620         pos.start++;
1621         int start = pos.start;
1622         boolean isFirst = true;
1623         
1624         while ( !startsWith( pos, SQUOTE ) )
1625         {
1626             if ( isFirst )
1627             {
1628                 isFirst = false;
1629                 
1630                 if ( !isEmpty( pos ) && isAlpha( pos ) ) 
1631                 {
1632                     // leadkeychar
1633                     pos.start++;
1634                 }
1635                 else
1636                 {
1637                     // Error, we are expecting a leadKeychar
1638                     throw new LdapSchemaException( I18n.err( I18n.ERR_13788_LEAD_KEY_CHAR_EXPECTED, 
1639                         pos.lineNumber, pos.start ) );
1640                 }
1641             }
1642             else
1643             {
1644                 if ( isEmpty( pos ) )
1645                 {
1646                     // This is an error
1647                     throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 
1648                         pos.lineNumber, pos.start ) );
1649                 }
1650                 
1651                 char c = pos.line.charAt( pos.start );
1652                 
1653                 if ( Character.isAlphabetic( c ) || Character.isDigit( c ) || ( c == HYPHEN ) )
1654                 {
1655                     pos.start++;
1656                 }
1657                 else
1658                 {
1659                     // This is an error
1660                     throw new LdapSchemaException( I18n.err( I18n.ERR_13791_KEYCHAR_EXPECTED, c, 
1661                         pos.lineNumber, pos.start ) );
1662                 }
1663             }
1664         }
1665         
1666         if ( startsWith( pos, SQUOTE ) )
1667         {
1668             // We are done, move one char forward to eliminate the simple quote
1669             pos.start++;
1670             
1671             return pos.line.substring( start, pos.start - 1 );
1672         }
1673         else
1674         {
1675             // No closing simple quote, this is an error
1676             throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 
1677                 pos.lineNumber, pos.start ) );
1678         }
1679     }
1680     
1681     
1682     /**
1683      * <pre>
1684      * qdescr ::== SQUOTE descr SQUOTE
1685      * descr ::= keystring
1686      * keystring ::= leadkeychar *keychar
1687      * leadkeychar ::= ALPHA
1688      * keychar ::= ALPHA | DIGIT | HYPHEN
1689      * </pre>
1690      * 
1691      * In quirksMode :
1692      * 
1693      * <pre>
1694      * qdescr ::== SQUOTE descr SQUOTE | descr | SQUOTE numericoid SQUOTE
1695      * descr ::= keystring
1696      * keystring ::= keychar+
1697      * keychar ::= ALPHA | DIGIT | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP 
1698      * </pre>
1699      * 
1700      * @param reader The stream reader
1701      * @param pos The position in the Schema
1702      * @return the QDescr
1703      * @throws IOException If the stream can't be read
1704      * @throws LdapSchemaException If the schema is wrong
1705      */
1706     private static String getQDescrRelaxed( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
1707     {
1708         if ( startsWith( reader, pos, SQUOTE ) )
1709         {
1710             pos.start++;
1711             int start = pos.start;
1712             
1713             while ( !startsWith( pos, SQUOTE ) )
1714             {
1715                 if ( isEmpty( pos ) )
1716                 {
1717                     throw new LdapSchemaException( I18n.err( I18n.ERR_13789_SIMPLE_QUOTE_EXPECTED_AT_START, 
1718                         pos.lineNumber, pos.start ) );
1719                 }
1720                 
1721                 char c = pos.line.charAt( pos.start );
1722                 
1723                 if ( Character.isDigit( c ) || Character.isAlphabetic( c ) || ( c == HYPHEN ) || ( c == UNDERSCORE )
1724                     || ( c == SEMI_COLON ) || ( c == DOT ) || ( c == COLON ) || ( c == SHARP ) )
1725                 {
1726                     pos.start++;
1727                 }
1728                 else if ( c != SQUOTE )
1729                 {
1730                     throw new LdapSchemaException( I18n.err( I18n.ERR_13790_NOT_A_KEYSTRING, pos.lineNumber, pos.start ) );
1731                 }
1732             }
1733             
1734             pos.start++;
1735             
1736             return pos.line.substring( start, pos.start - 1 );
1737         }
1738         else
1739         {
1740             int start = pos.start;
1741             
1742             while ( !isEmpty( pos ) )
1743             {
1744                 char c = pos.line.charAt( pos.start );
1745 
1746                 if ( Character.isDigit( c ) || Character.isAlphabetic( c ) || ( c == HYPHEN ) || ( c == UNDERSCORE )
1747                     || ( c == SEMI_COLON ) || ( c == DOT ) || ( c == COLON ) || ( c == SHARP ) )
1748                 {
1749                     pos.start++;
1750                 }
1751                 else
1752                 {
1753                     break;
1754                 }
1755             }
1756 
1757             return pos.line.substring( start, pos.start );
1758         }
1759     }
1760     
1761     
1762     /**
1763      * No relaxed version.
1764      * <pre>
1765      * qdstring ::== SQUOTE dstring SQUOTE
1766      * dstring  ::= ( QS | QQ | QUTF8 )+            ; escaped UTF-8 string
1767      * QS       ::= ESC %x35 ( %x43 | %x63 )        ; "\5C" | "\5c", escape char
1768      * QQ       ::= ESC %x32 %x37                   ; "\27", simple quote char
1769      * QUTF8    ::= QUTF1 | UTFMB
1770      * QUTF1    ::= %x00-26 | %x28-5B | %x5D-7F     ; All ascii but ' and \
1771      * UTFMB    ::= UTF2 | UTF3 | UTF4
1772      * UTF0     ::= %x80-BF
1773      * UTF2     ::= %xC2-DF UTF0
1774      * UTF3     ::= %xE0 %xA0-BF UTF0 | %xE1-EC UTF0 UTF0 | %xED %x80-9F UTF0 | %xEE-EF UTF0 UTF0
1775      * UTF4     ::= %xF0 %x90-BF UTF0 UTF0 | %xF1-F3 UTF0 UTF0 UTF0 | %xF4 %x80-8F UTF0 UTF0
1776      * ESC      ::= %x5C                            ; backslash ("\")
1777      * </pre>
1778      * 
1779      * @param reader The stream reader
1780      * @param pos The position in the Schema
1781      * @return The QDString
1782      * @throws LdapSchemaException If the schema is wrong
1783      * @throws IOException If the stream can't be read
1784      */
1785     private static String getQDString( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
1786     {
1787         // The first quote
1788         if ( !startsWith( reader, pos, SQUOTE ) )
1789         {
1790             throw new LdapSchemaException( I18n.err( I18n.ERR_13789_SIMPLE_QUOTE_EXPECTED_AT_START, 
1791                 pos.lineNumber, pos.start ) );
1792         }
1793         
1794         pos.start++;
1795         int start = pos.start;
1796         int nbEscapes = 0;
1797         
1798         while ( !isEmpty( pos ) && !startsWith( pos, SQUOTE ) )
1799         {
1800             // At the moment, just swallow anything
1801             if ( startsWith( pos, ESCAPE ) )
1802             {
1803                 nbEscapes++;
1804             }
1805             
1806             pos.start++;
1807             
1808         }
1809         
1810         if ( startsWith( pos, SQUOTE ) )
1811         {
1812             // We are done, move one char forward to eliminate the simple quote
1813             pos.start++;
1814             
1815             // Now, un-escape the escaped chars
1816             char[] unescaped = new char[pos.start - 1 - start - nbEscapes * 2];
1817             int newPos = 0;
1818             
1819             for ( int i = start; i < pos.start - 1; i++ )
1820             {
1821                 char c = pos.line.charAt( i );
1822                 
1823                 if ( c == ESCAPE )
1824                 {
1825                     if ( i + 2 > pos.start )
1826                     {
1827                         // Error : not enough hex value
1828                         throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 
1829                             pos.lineNumber, pos.start ) );
1830                     }
1831                     
1832                     int u = Character.digit( pos.line.charAt( i + 1 ), 16 );
1833                     int l = Character.digit( pos.line.charAt( i + 2 ), 16 );
1834 
1835                     unescaped[newPos] = ( char ) ( ( u << 4 ) + l );
1836                     i += 2;
1837                 }
1838                 else
1839                 {
1840                     unescaped[newPos] = c;
1841                 }
1842                 
1843                 newPos++;
1844             }
1845             
1846             return new String( unescaped );
1847         }
1848         else
1849         {
1850             // No closing simple quote, this is an error
1851             throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 
1852                 pos.lineNumber, pos.start ) );
1853         }
1854     }
1855 
1856 
1857     /**
1858      * <pre>
1859      * qdescrs ::= qdescr | LPAREN WSP qdescrlist WSP RPAREN
1860      * qdescrlist ::= [ qdescr *( SP qdescr ) ]
1861      * qdescr ::== SQUOTE descr SQUOTE
1862      * descr ::= keystring
1863      * keystring ::= leadkeychar *keychar
1864      * leadkeychar ::= ALPHA
1865      * keychar ::= ALPHA / DIGIT / HYPHEN
1866      * </pre>
1867      * 
1868      * @param reader The stream reader
1869      * @param pos The position in the Schema
1870      * @param relaxed If the schema is to be processed in relaxed mode
1871      * @return The list of QDescr
1872      * @throws LdapSchemaException If the schema is wrong
1873      * @throws IOException If the stream can't be read
1874      */
1875     private static List<String> getQDescrs( Reader reader, PosSchema pos, boolean relaxed ) 
1876             throws LdapSchemaException, IOException
1877     {
1878         List<String> qdescrs = new ArrayList<>();
1879         
1880         // It may start with a '('
1881         if ( startsWith( reader, pos, LPAREN ) )
1882         {
1883             pos.start++;
1884             
1885             // We have more than a name
1886             skipWhites( reader, pos, false );
1887             
1888             while ( !startsWith( reader, pos, RPAREN ) )
1889             {
1890                 String qdescr;
1891                 
1892                 if ( relaxed )
1893                 {
1894                     qdescr = getQDescrRelaxed( reader, pos );
1895                 }
1896                 else
1897                 {
1898                     qdescr = getQDescrStrict( reader, pos );
1899                 }
1900                 
1901                 qdescrs.add( qdescr );
1902                 
1903                 if ( startsWith( reader, pos, RPAREN ) )
1904                 {
1905                     break;
1906                 }
1907                 
1908                 skipWhites( reader, pos, true );
1909             }
1910             
1911             if ( !startsWith( reader, pos, RPAREN ) )
1912             {
1913                 throw new LdapSchemaException( I18n.err( I18n.ERR_13793_NO_CLOSING_PAREN, 
1914                     pos.lineNumber, pos.start ) );
1915             }
1916             
1917             pos.start++;
1918         }
1919         else
1920         {
1921             // Only one name, read it
1922             String qDescr;
1923             
1924             if ( relaxed )
1925             {
1926                 qDescr = getQDescrRelaxed( reader, pos );
1927             }
1928             else
1929             {
1930                 qDescr = getQDescrStrict( reader, pos );
1931             }
1932             
1933             if ( Strings.isEmpty( qDescr ) )
1934             {
1935                 throw new LdapSchemaException( I18n.err( I18n.ERR_13732_NAME_CANNOT_BE_NULL, pos.lineNumber, pos.start ) );
1936             }
1937             
1938             qdescrs.add( qDescr );
1939         }
1940         
1941         return qdescrs;
1942     }
1943 
1944 
1945     /**
1946      * <pre>
1947      * qdstrings    ::= qdstring | ( LPAREN WSP qdstringlist WSP RPAREN )
1948      * qdstringlist ::= qdstring *( SP qdstring )*
1949      * qdstring     ::= SQUOTE dstring SQUOTE
1950      * dstring      ::= 1*( QS / QQ / QUTF8 )   ; escaped UTF-8 string
1951      * </pre>
1952      * 
1953      * @param reader The stream reader
1954      * @param pos The position in the Schema
1955      * @return The list of QDString
1956      * @throws LdapSchemaException If the schema is wrong
1957      * @throws IOException If the stream can't be read
1958      */
1959     private static List<String> getQDStrings( Reader reader, PosSchema pos ) 
1960         throws LdapSchemaException, IOException
1961     {
1962         List<String> qdStrings = new ArrayList<>();
1963         
1964         // It may start with a '('
1965         if ( startsWith( reader, pos, LPAREN ) )
1966         {
1967             pos.start++;
1968             
1969             // We have more than a name
1970             skipWhites( reader, pos, false );
1971             
1972             while ( !startsWith( reader, pos, RPAREN ) )
1973             {
1974                 qdStrings.add( getQDString( reader, pos ) );
1975                 
1976                 if ( startsWith( reader, pos, RPAREN ) )
1977                 {
1978                     break;
1979                 }
1980                 
1981                 skipWhites( reader, pos, true );
1982             }
1983             
1984             if ( !startsWith( reader, pos, RPAREN ) )
1985             {
1986                 throw new LdapSchemaException( I18n.err( I18n.ERR_13793_NO_CLOSING_PAREN, 
1987                     pos.lineNumber, pos.start ) );
1988             }
1989             
1990             pos.start++;
1991         }
1992         else
1993         {
1994             // Only one name, read it
1995             qdStrings.add( getQDString( reader, pos ) );
1996         }
1997         
1998         return qdStrings;
1999     }
2000 
2001     
2002     /**
2003      * <pre>
2004      * oids     ::= oid | ( LPAREN WSP oidlist WSP RPAREN )
2005      * oidlist  ::= oid *( WSP DOLLAR WSP oid )
2006      * </pre>
2007      * 
2008      * @param reader The stream reader
2009      * @param pos The position in the Schema
2010      * @return The list of OIDs
2011      * @throws LdapSchemaException If the schema is wrong
2012      * @throws IOException If the stream can't be read
2013      */
2014     private static List<String> getOidsStrict( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
2015     {
2016         List<String> oids = new ArrayList<>();
2017         
2018         // It may start with a '('
2019         if ( startsWith( reader, pos, LPAREN ) )
2020         {
2021             pos.start++;
2022             
2023             // We have more than a name
2024             skipWhites( reader, pos, false );
2025             boolean moreExpected = false;
2026             
2027             while ( !startsWith( reader, pos, RPAREN ) )
2028             {
2029                 moreExpected = false;
2030                 
2031                 oids.add( getOidStrict( pos ) );
2032                 
2033                 if ( startsWith( reader, pos, RPAREN ) )
2034                 {
2035                     break;
2036                 }
2037                 
2038                 skipWhites( reader, pos, false );
2039                 
2040                 if ( startsWith( reader, pos, DOLLAR ) )
2041                 {
2042                     pos.start++;
2043                     moreExpected = true;
2044                 }
2045 
2046                 skipWhites( reader, pos, false );
2047             }
2048             
2049             if ( !startsWith( reader, pos, RPAREN ) )
2050             {
2051                 throw new LdapSchemaException( I18n.err( I18n.ERR_13793_NO_CLOSING_PAREN, 
2052                     pos.lineNumber, pos.start ) );
2053             }
2054             
2055             if ( moreExpected )
2056             {
2057                 throw new LdapSchemaException( I18n.err( I18n.ERR_13794_MORE_OIDS_EXPECTED, 
2058                     pos.lineNumber, pos.start ) );
2059             }
2060             
2061             pos.start++;
2062         }
2063         else
2064         {
2065             // Only one name, read it
2066             oids.add( getOidStrict( pos ) );
2067         }
2068         
2069         return oids;
2070     }
2071 
2072     
2073     /**
2074      * <pre>
2075      * oids     ::= oid | ( LPAREN WSP oidlist WSP RPAREN )
2076      * oidlist  ::= oid *( WSP DOLLAR WSP oid )
2077      * </pre>
2078      * 
2079      * @param reader The stream reader
2080      * @param pos The position in the Schema
2081      * @return The list of OIDs
2082      * @throws LdapSchemaException If the schema is wrong
2083      * @throws IOException If the stream can't be read
2084      */
2085     private static List<String> getOidsRelaxed( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
2086     {
2087         List<String> oids = new ArrayList<>();
2088         
2089         // It may start with a '('
2090         if ( startsWith( reader, pos, LPAREN ) )
2091         {
2092             pos.start++;
2093             
2094             // We have more than a name
2095             skipWhites( reader, pos, false );
2096             boolean moreExpected = false;
2097             
2098             while ( !startsWith( reader, pos, RPAREN ) )
2099             {
2100                 moreExpected = false;
2101                 
2102                 oids.add( getOidRelaxed( pos, UN_QUOTED ) );
2103                 
2104                 if ( startsWith( reader, pos, RPAREN ) )
2105                 {
2106                     break;
2107                 }
2108                 
2109                 skipWhites( reader, pos, false );
2110                 
2111                 if ( startsWith( reader, pos, DOLLAR ) )
2112                 {
2113                     pos.start++;
2114                     moreExpected = true;
2115                 }
2116 
2117                 skipWhites( reader, pos, false );
2118             }
2119             
2120             if ( !startsWith( reader, pos, RPAREN ) )
2121             {
2122                 throw new LdapSchemaException( I18n.err( I18n.ERR_13793_NO_CLOSING_PAREN, 
2123                     pos.lineNumber, pos.start ) );
2124             }
2125             
2126             if ( moreExpected )
2127             {
2128                 throw new LdapSchemaException( I18n.err( I18n.ERR_13794_MORE_OIDS_EXPECTED, 
2129                     pos.lineNumber, pos.start ) );
2130             }
2131             
2132             pos.start++;
2133         }
2134         else
2135         {
2136             // Only one name, read it
2137             oids.add( getOidRelaxed( pos, UN_QUOTED ) );
2138         }
2139         
2140         return oids;
2141     }
2142 
2143     
2144     /**
2145      * <pre>
2146      * noidlen = oidStrict [ LCURLY len RCURLY ]
2147      * </pre>
2148      *  
2149      * @param attributeType The AttributeType
2150      * @param pos The position in the Schema
2151      * @throws LdapSchemaException If the schema is wrong
2152      */
2153     private static void getNoidLenStrict( AttributeType attributeType, PosSchema pos ) throws LdapSchemaException
2154     {
2155         // Get the oid
2156         String oid = getOidStrict( pos );
2157         
2158         if ( oid.length() == 0 )
2159         {
2160             throw new LdapSchemaException( I18n.err( I18n.ERR_13828_MISSING_SYNTAX_OID, pos.line, pos.start ) );
2161         }
2162         
2163         attributeType.setSyntaxOid( oid );
2164 
2165         // Then the len, if any
2166         if ( startsWith( pos, LBRACE ) )
2167         {
2168             pos.start++;
2169             int start = pos.start;
2170             
2171             while ( !isEmpty( pos ) && isDigit( pos ) )
2172             {
2173                 pos.start++;
2174             }
2175             
2176             if ( startsWith( pos, RBRACE ) )
2177             {
2178                 String lenStr = pos.line.substring( start, pos.start );
2179                 
2180                 if ( lenStr.length() == 0 )
2181                 {
2182                     throw new LdapSchemaException( I18n.err( I18n.ERR_13827_EMPTY_SYNTAX_LEN, pos.line, pos.start ) );
2183                 }
2184                 
2185                 pos.start++;
2186                 
2187                 if ( Strings.isEmpty( lenStr ) )
2188                 {
2189                     attributeType.setSyntaxLength( -1L );
2190                 }
2191                 else
2192                 {
2193                     attributeType.setSyntaxLength( Long.parseLong( lenStr ) );
2194                 }
2195             }
2196             else
2197             {
2198                 // The opening curly hasn't been closed
2199                 throw new LdapSchemaException( I18n.err( I18n.ERR_13795_OPENED_BRACKET_NOT_CLOSED, 
2200                     pos.lineNumber, pos.start ) );
2201             }
2202         }
2203     }
2204 
2205     
2206     /**
2207      * <pre>
2208      * noidlen = oidRelaxed [ LCURLY len RCURLY ]
2209      * </pre>
2210      * 
2211      * @param attributeType The AttributeType
2212      * @param pos The position in the Schema
2213      * @throws LdapSchemaException If the schema is wrong
2214      */
2215     private static void getNoidLenRelaxed( AttributeType attributeType, PosSchema pos ) throws LdapSchemaException
2216     {
2217         // Check for quotes
2218         boolean hasQuote = false;
2219 
2220         char c = pos.line.charAt( pos.start );
2221         
2222         if ( c == SQUOTE )
2223         {
2224             hasQuote = true;
2225             pos.start++;
2226 
2227             if ( isEmpty( pos ) )
2228             {
2229                 throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 
2230                     pos.lineNumber, pos.start ) );
2231             }
2232         }
2233 
2234         // Get the oid
2235         String oid = getOidRelaxed( pos, hasQuote );
2236         
2237         if ( oid.length() == 0 )
2238         {
2239             throw new LdapSchemaException( I18n.err( I18n.ERR_13828_MISSING_SYNTAX_OID, pos.line, pos.start ) );
2240         }
2241         
2242         attributeType.setSyntaxOid( oid );
2243 
2244         // Then the len, if any
2245         if ( startsWith( pos, LBRACE ) )
2246         {
2247             pos.start++;
2248             int start = pos.start;
2249             
2250             while ( !isEmpty( pos ) && isDigit( pos ) )
2251             {
2252                 pos.start++;
2253             }
2254             
2255             if ( startsWith( pos, RBRACE ) )
2256             {
2257                 String lenStr = pos.line.substring( start, pos.start );
2258                 
2259                 pos.start++;
2260                 
2261                 if ( Strings.isEmpty( lenStr ) )
2262                 {
2263                     attributeType.setSyntaxLength( -1L );
2264                 }
2265                 else
2266                 {
2267                     attributeType.setSyntaxLength( Long.parseLong( lenStr ) );
2268                 }
2269             }
2270             else
2271             {
2272                 // The opening curly hasn't been closed
2273                 throw new LdapSchemaException( I18n.err( I18n.ERR_13795_OPENED_BRACKET_NOT_CLOSED, 
2274                     pos.lineNumber, pos.start ) );
2275             }
2276         }
2277         
2278         if ( hasQuote )
2279         {
2280             if ( isEmpty( pos ) )
2281             {
2282                 throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 
2283                     pos.lineNumber, pos.start ) );
2284             }
2285             
2286             c = pos.line.charAt( pos.start );
2287             
2288             if ( c == SQUOTE )
2289             {
2290                pos.start++;
2291            }
2292            else
2293            {
2294                throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 
2295                    pos.lineNumber, pos.start ) );
2296            }
2297         }
2298     }
2299     
2300 
2301     
2302     /**
2303      * <pre>
2304      * ruleid ::= number
2305      * number ::= DIGIT | LDIGIT DIGIT+
2306      * DIGIT  ::= [0-9]
2307      * LDIGIT ::= [1-9]
2308      * </pre>
2309      * 
2310      * @param pos The position in the Schema
2311      * @return The RuleID
2312      * @throws LdapSchemaException If the schema is wrong
2313      */
2314     private static int getRuleId( PosSchema pos ) throws LdapSchemaException
2315     {
2316         int start = pos.start;
2317 
2318         while ( !isEmpty( pos ) && isDigit( pos ) )
2319         {
2320             pos.start++;
2321         }
2322         
2323         if ( start == pos.start )
2324         {
2325             // No ruleID
2326             throw new LdapSchemaException( I18n.err( I18n.ERR_13811_INVALID_RULE_ID, 
2327                 pos.lineNumber, pos.start ) );
2328         }
2329 
2330         String lenStr = pos.line.substring( start, pos.start );
2331         
2332         return Integer.parseInt( lenStr );
2333     }
2334 
2335     
2336     /**
2337      * <pre>
2338      * ruleids      ::= ruleid | ( LPAREN WSP ruleidlist WSP RPAREN )
2339      * ruleidlist   ::= ruleid ( SP ruleid )*
2340      * </pre>
2341      * 
2342      * @param reader The stream reader
2343      * @param pos The position in the Schema
2344      * @return The list of RuleIDs
2345      * @throws LdapSchemaException If the schema is wrong
2346      * @throws IOException If the stream can't be read
2347      */
2348     private static List<Integer> getRuleIds( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
2349     {
2350         List<Integer> ruleIds = new ArrayList<>();
2351         
2352         // It may start with a '('
2353         if ( startsWith( reader, pos, LPAREN ) )
2354         {
2355             pos.start++;
2356             
2357             // We may have more than a ruleid
2358             skipWhites( reader, pos, false );
2359             boolean moreExpected = false;
2360             
2361             while ( !startsWith( reader, pos, RPAREN ) )
2362             {
2363                 moreExpected = false;
2364                 
2365                 ruleIds.add( getRuleId( pos ) );
2366                 
2367                 if ( startsWith( reader, pos, RPAREN ) )
2368                 {
2369                     break;
2370                 }
2371                 
2372                 skipWhites( reader, pos, false );
2373                 
2374                 if ( startsWith( reader, pos, DOLLAR ) )
2375                 {
2376                     pos.start++;
2377                     moreExpected = true;
2378                 }
2379 
2380                 skipWhites( reader, pos, false );
2381             }
2382             
2383             if ( !startsWith( reader, pos, RPAREN ) )
2384             {
2385                 throw new LdapSchemaException( I18n.err( I18n.ERR_13793_NO_CLOSING_PAREN, 
2386                     pos.lineNumber, pos.start ) );
2387             }
2388             
2389             if ( moreExpected )
2390             {
2391                 throw new LdapSchemaException( I18n.err( I18n.ERR_13813_MORE_RULE_IDS_EXPECTED, 
2392                     pos.lineNumber, pos.start ) );
2393             }
2394             
2395             pos.start++;
2396         }
2397         else
2398         {
2399             // Only one ruleId, read it
2400             ruleIds.add( getRuleId( pos ) );
2401         }
2402         
2403         return ruleIds;
2404     }
2405     
2406     
2407     /**
2408      * 
2409      * @param pos The position in the Schema
2410      * @return The USAGE
2411      * @throws LdapSchemaException If the schema is wrong
2412      */
2413     private static UsageEnum getUsageStrict( PosSchema pos ) throws LdapSchemaException
2414     {
2415         if ( isEmpty( pos ) )
2416         {
2417             throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED, 
2418                 pos.lineNumber, pos.start ) );
2419         }
2420         
2421         if ( startsWith( pos, USER_APPLICATIONS_STR ) )
2422         { 
2423             pos.start += USER_APPLICATIONS_STR.length();
2424             
2425             return UsageEnum.USER_APPLICATIONS;
2426         }
2427         else if ( startsWith( pos, DIRECTORY_OPERATION_STR ) )
2428         {
2429             pos.start += DIRECTORY_OPERATION_STR.length();
2430             
2431             return UsageEnum.DIRECTORY_OPERATION;
2432         }
2433         else if ( startsWith( pos, DISTRIBUTED_OPERATION_STR ) )
2434         { 
2435             pos.start += DISTRIBUTED_OPERATION_STR.length();
2436             
2437             return UsageEnum.DISTRIBUTED_OPERATION;
2438         }
2439         else if ( startsWith( pos, DSA_OPERATION_STR ) )
2440         { 
2441             pos.start += DSA_OPERATION_STR.length();
2442 
2443             return UsageEnum.DSA_OPERATION;
2444         }
2445         else
2446         {
2447             throw new LdapSchemaException( I18n.err( I18n.ERR_13797_USAGE_UNKNOWN, 
2448                 pos.lineNumber, pos.start ) );
2449         }
2450     }
2451     
2452     
2453     /**
2454      * 
2455      * @param pos The position in the Schema
2456      * @return The USAGE
2457      * @throws LdapSchemaException If the schema is wrong
2458      */
2459     private static UsageEnum getUsageRelaxed( PosSchema pos ) throws LdapSchemaException
2460     {
2461         if ( isEmpty( pos ) )
2462         {
2463             throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED, 
2464                 pos.lineNumber, pos.start ) );
2465         }
2466         
2467         boolean isSQuoted = false;
2468         boolean isDQuoted = false;
2469         
2470         if ( pos.line.charAt( pos.start ) == SQUOTE )
2471         {
2472             isSQuoted = true;
2473             pos.start++;
2474 
2475             if ( isEmpty( pos ) )
2476             {
2477                 throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED, 
2478                     pos.lineNumber, pos.start ) );
2479             }
2480         }
2481         else if ( pos.line.charAt( pos.start ) == DQUOTE )
2482         {
2483             isDQuoted = true;
2484             pos.start++;
2485 
2486             if ( isEmpty( pos ) )
2487             {
2488                 throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED, 
2489                     pos.lineNumber, pos.start ) );
2490             }
2491         }
2492 
2493         UsageEnum usage = UsageEnum.USER_APPLICATIONS;
2494 
2495         if ( startsWith( pos, USER_APPLICATIONS_STR ) )
2496         { 
2497             pos.start += USER_APPLICATIONS_STR.length();
2498             
2499             usage = UsageEnum.USER_APPLICATIONS;
2500         }
2501         else if ( startsWith( pos, DIRECTORY_OPERATION_STR ) )
2502         {
2503             pos.start += DIRECTORY_OPERATION_STR.length();
2504             
2505             usage = UsageEnum.DIRECTORY_OPERATION;
2506         } 
2507         else if ( startsWith( pos, DISTRIBUTED_OPERATION_STR ) )
2508         { 
2509             pos.start += DISTRIBUTED_OPERATION_STR.length();
2510             
2511             usage = UsageEnum.DISTRIBUTED_OPERATION;
2512         } 
2513         else if ( startsWith( pos, DSA_OPERATION_STR ) )
2514         { 
2515             pos.start += DSA_OPERATION_STR.length();
2516 
2517             usage = UsageEnum.DSA_OPERATION;
2518         } 
2519         else
2520         {
2521             throw new LdapSchemaException( I18n.err( I18n.ERR_13797_USAGE_UNKNOWN, 
2522                 pos.lineNumber, pos.start ) );
2523         }
2524         
2525         if ( isSQuoted )
2526         {
2527             if ( isEmpty( pos ) )
2528             {
2529                 throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED, 
2530                     pos.lineNumber, pos.start ) );
2531             }
2532             
2533             if ( pos.line.charAt( pos.start ) != SQUOTE )
2534             {
2535                 throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 
2536                     pos.lineNumber, pos.start ) );
2537             }
2538             
2539             pos.start++;
2540         }
2541         else if ( isDQuoted )
2542         {
2543             if ( isEmpty( pos ) )
2544             {
2545                 throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED, 
2546                     pos.lineNumber, pos.start ) );
2547             }
2548             
2549             if ( pos.line.charAt( pos.start ) != DQUOTE )
2550             {
2551                 throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 
2552                     pos.lineNumber, pos.start ) );
2553             }
2554             
2555             pos.start++;
2556         }
2557         
2558         return usage;
2559     }
2560 
2561     
2562     /**
2563      * <pre>
2564      * extension    ::= xstring SP qdstrings
2565      * xstring      ::= "X" HYPHEN ( ALPHA | HYPHEN | USCORE )+
2566      * qdstrings    ::= qdstring | ( LPAREN WSP qdstringlist WSP RPAREN )
2567      * qdstringlist ::= qdstring *( SP qdstring )*
2568      * qdstring     ::= SQUOTE dstring SQUOTE
2569      * dstring      ::= 1*( QS / QQ / QUTF8 )   ; escaped UTF-8 string
2570      * </pre>
2571      * 
2572      * @param reader The stream reader
2573      * @param pos The position in the Schema
2574      * @param schemaObject The SchemaObject
2575      * @throws IOException If the stream can't be read
2576      * @throws LdapSchemaException If the schema is wrong
2577      */
2578     private static void processExtension( Reader reader, PosSchema pos, SchemaObject schemaObject ) 
2579         throws LdapSchemaException, IOException
2580     {
2581         // The xstring first
2582         String extensionKey = getXString( pos );
2583         
2584         skipWhites( reader, pos, true );
2585         
2586         List<String> extensionValues = getQDStrings( reader, pos );
2587         
2588         if ( schemaObject.hasExtension( extensionKey ) )
2589         {
2590             throw new LdapSchemaException( 
2591                 I18n.err( I18n.ERR_13780_SCHEMA_OBJECT_DESCRIPTION_HAS_ELEMENT_TWICE, extensionKey, 
2592                 pos.lineNumber, pos.start ) );
2593         }
2594 
2595         schemaObject.addExtension( extensionKey, extensionValues );
2596     }
2597     
2598     
2599     /**
2600      * <pre>
2601      * xstring      ::= "X" HYPHEN ( ALPHA | HYPHEN | USCORE )+
2602      * </pre>
2603      * 
2604      * @param pos The position in the Schema
2605      * @return the X-String
2606      * @throws LdapSchemaException If the schema is wrong
2607      */
2608     private static String getXString( PosSchema pos ) throws LdapSchemaException
2609     {
2610         int start = pos.start;
2611         
2612         if ( startsWith( pos, EXTENSION_PREFIX ) )
2613         {
2614             pos.start += 2;
2615             
2616             // Now parse the remaining string
2617             while ( !isEmpty( pos ) && ( isAlpha( pos ) || startsWith( pos, HYPHEN ) || startsWith( pos, UNDERSCORE ) ) )
2618             {
2619                 pos.start++;
2620             }
2621             
2622             return pos.line.substring( start, pos.start );
2623         }
2624         else
2625         {
2626             throw new LdapSchemaException( I18n.err( I18n.ERR_13802_EXTENSION_SHOULD_START_WITH_X, 
2627                 pos.lineNumber, pos.start ) );
2628         }
2629     }
2630     
2631     
2632     /**
2633      * A FQCN
2634      * <pre>
2635      * FQCN ::= FQCN_IDENTIFIER ( '.' FQCN_IDENTIFIER )*
2636      * FQCN_IDENTIFIER ::= ( JavaLetter ( JavaLetterOrDigit )*
2637      * </pre>
2638      * 
2639      * @param pos The position in the Schema
2640      * @return The FQCN
2641      * @throws LdapSchemaException If the schema is wrong
2642      */
2643     private static String getFqcn( PosSchema pos ) throws LdapSchemaException
2644     {
2645         if ( ( pos.line == null ) || ( pos.line.length() - pos.start < 1 ) )
2646         {
2647             return "";
2648         }
2649 
2650         int start = pos.start;
2651         boolean isFirst = true;
2652         boolean dotSeen = false;
2653         
2654         while ( true )
2655         {
2656             char c = pos.line.charAt( pos.start );
2657             
2658             if ( isFirst )
2659             {
2660                 if ( !Character.isJavaIdentifierStart( c ) )
2661                 {
2662                     throw new LdapSchemaException( I18n.err( I18n.ERR_13822_INVALID_FQCN_BAD_IDENTIFIER_START, 
2663                         pos.lineNumber, pos.start ) );
2664                 }
2665                 
2666                 isFirst = false;
2667                 dotSeen = false;
2668                 pos.start++;
2669             }
2670             else
2671             {
2672                 if ( c == DOT ) 
2673                 {
2674                     if ( dotSeen )
2675                     {
2676                         throw new LdapSchemaException( I18n.err( I18n.ERR_13823_INVALID_FQCN_DOUBLE_DOT, 
2677                             pos.lineNumber, pos.start ) );
2678                     }
2679                     else
2680                     {
2681                         isFirst = true;
2682                         dotSeen = true;
2683                         pos.start++;
2684                     }
2685                 }
2686                 else
2687                 {
2688                     if ( Character.isJavaIdentifierPart( c ) )
2689                     {
2690                         pos.start++;
2691                         dotSeen = false;
2692                     }
2693                     else
2694                     {
2695                         return pos.line.substring( start, pos.start );
2696                     }
2697                 }
2698             }
2699             
2700             if ( pos.line.length() - pos.start < 1 )
2701             {
2702                 return pos.line.substring( start, pos.start );
2703             }
2704         }
2705     }
2706 
2707     
2708     /**
2709      * A base64 string
2710      * <pre>
2711      * byteCode ::= ( [a-z] | [A-Z] | [0-9] | '+' | '/' | '=' )*
2712      * </pre>
2713      * 
2714      * @param pos The position in the Schema
2715      * @return The ByteCode
2716      */
2717     private static String getByteCode( PosSchema pos )
2718     {
2719         if ( ( pos.line == null ) || ( pos.line.length() - pos.start < 1 ) )
2720         {
2721             return "";
2722         }
2723 
2724         int start = pos.start;
2725         
2726         
2727         while ( !isEmpty( pos ) && ( isAlpha( pos ) || isDigit( pos ) || startsWith( pos, PLUS ) 
2728             || startsWith( pos, SLASH ) || startsWith( pos, EQUAL ) ) )
2729         {
2730             pos.start++;
2731             
2732             if ( ( pos.line == null ) || ( pos.line.length() - pos.start < 1 ) )
2733             {
2734                 return pos.line.substring( start, pos.start );
2735             }
2736         }
2737         
2738         return pos.line.substring( start, pos.start );
2739     }
2740     
2741     
2742     /**
2743      * 
2744      * @param elementsSeen The elements that have been processed already
2745      * @param element The current element
2746      * @param pos T he position in the Schema
2747      * @return The elements we have just processed
2748      * @throws LdapSchemaException If the schema is wrong
2749      */
2750     private static int checkElement( int elementsSeen, SchemaObjectElements element, PosSchema pos ) throws LdapSchemaException
2751     {
2752         if ( ( elementsSeen & element.getValue() ) != 0 )
2753         {
2754             throw new LdapSchemaException( I18n.err( I18n.ERR_13780_SCHEMA_OBJECT_DESCRIPTION_HAS_ELEMENT_TWICE, 
2755                 element, pos.lineNumber, pos.start ) );
2756         }
2757         
2758         elementsSeen |= element.getValue();
2759         
2760         return elementsSeen;
2761     }
2762 
2763     
2764     /**
2765      * Production for matching attribute type descriptions. It is fault-tolerant
2766      * against element ordering.
2767      *
2768      * <pre>
2769      * AttributeTypeDescription = LPAREN WSP
2770      *     numericoid                    ; object identifier
2771      *     [ SP "NAME" SP qdescrs ]      ; short names (descriptors)
2772      *     [ SP "DESC" SP qdstring ]     ; description
2773      *     [ SP "OBSOLETE" ]             ; not active
2774      *     [ SP "SUP" SP oid ]           ; supertype
2775      *     [ SP "EQUALITY" SP oid ]      ; equality matching rule
2776      *     [ SP "ORDERING" SP oid ]      ; ordering matching rule
2777      *     [ SP "SUBSTR" SP oid ]        ; substrings matching rule
2778      *     [ SP "SYNTAX" SP noidlen ]    ; value syntax
2779      *     [ SP "SINGLE-VALUE" ]         ; single-value
2780      *     [ SP "COLLECTIVE" ]           ; collective
2781      *     [ SP "NO-USER-MODIFICATION" ] ; not user modifiable
2782      *     [ SP "USAGE" SP usage ]       ; usage
2783      *     extensions WSP RPAREN         ; extensions
2784      * 
2785      * usage = "userApplications"     /  ; user
2786      *         "directoryOperation"   /  ; directory operational
2787      *         "distributedOperation" /  ; DSA-shared operational
2788      *         "dSAOperation"            ; DSA-specific operational     
2789      * 
2790      * extensions = *( SP xstring SP qdstrings )
2791      * xstring = "X" HYPHEN 1*( ALPHA / HYPHEN / USCORE ) 
2792      * </pre>
2793      * 
2794      * @param attributeTypeDescription The String containing the AttributeTypeDescription
2795      * @return An instance of AttributeType
2796      * @throws ParseException If the element was invalid
2797      */
2798     public AttributeType parseAttributeType( String attributeTypeDescription ) throws ParseException
2799     {
2800         if ( ( attributeTypeDescription == null ) || Strings.isEmpty( attributeTypeDescription.trim() ) )
2801         {
2802             throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
2803         }
2804         
2805         try ( Reader reader = new BufferedReader( new StringReader( attributeTypeDescription ) ) )
2806         {
2807             PosSchema pos = new PosSchema();
2808 
2809             if ( isQuirksModeEnabled )
2810             {
2811                 return parseAttributeTypeRelaxed( reader, pos, objectIdentifierMacros );
2812             }
2813             else
2814             {
2815                 return parseAttributeTypeStrict( reader, pos, objectIdentifierMacros );
2816             }
2817         }
2818         catch ( IOException | LdapSchemaException e )
2819         {
2820             // This exception is not passed as a cause in ParseException. Therefore at least log in, so it won't be lost.
2821             LOG.trace( I18n.err( I18n.ERR_13865_ERROR_PARSING_AT, attributeTypeDescription, e.getMessage() ), e );
2822             throw new ParseException( e.getMessage(), 0 );
2823         }
2824     }
2825 
2826     
2827     /**
2828      * Production for matching attribute type descriptions. It is fault-tolerant
2829      * against element ordering. It's strict.
2830      *
2831      * <pre>
2832      * AttributeTypeDescription = LPAREN WSP
2833      *     numericoid                    ; object identifier
2834      *     [ SP "NAME" SP qdescrs ]      ; short names (descriptors)
2835      *     [ SP "DESC" SP qdstring ]     ; description
2836      *     [ SP "OBSOLETE" ]             ; not active
2837      *     [ SP "SUP" SP oid ]           ; supertype
2838      *     [ SP "EQUALITY" SP oid ]      ; equality matching rule
2839      *     [ SP "ORDERING" SP oid ]      ; ordering matching rule
2840      *     [ SP "SUBSTR" SP oid ]        ; substrings matching rule
2841      *     [ SP "SYNTAX" SP noidlen ]    ; value syntax
2842      *     [ SP "SINGLE-VALUE" ]         ; single-value
2843      *     [ SP "COLLECTIVE" ]           ; collective
2844      *     [ SP "NO-USER-MODIFICATION" ] ; not user modifiable
2845      *     [ SP "USAGE" SP usage ]       ; usage
2846      *     extensions WSP RPAREN         ; extensions
2847      * 
2848      * usage = "userApplications"     /  ; user
2849      *         "directoryOperation"   /  ; directory operational
2850      *         "distributedOperation" /  ; DSA-shared operational
2851      *         "dSAOperation"            ; DSA-specific operational     
2852      * 
2853      * extensions = *( SP xstring SP qdstrings )
2854      * xstring = "X" HYPHEN 1*( ALPHA / HYPHEN / USCORE ) 
2855      * </pre>
2856      * 
2857      * @param reader The stream reader
2858      * @param pos The position in the Schema
2859      * @param objectIdentifierMacros The set of existing Macros
2860      * @return An instance of AttributeType
2861      * @throws IOException If the stream can't be read
2862      * @throws LdapSchemaException If the schema is wrong
2863      */
2864     private static AttributeType parseAttributeTypeStrict( Reader reader, PosSchema pos,
2865         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException
2866     {
2867         // Get rid of whites, comments end empty lines
2868         skipWhites( reader, pos, false );
2869         
2870         // we must have a '('
2871         if ( pos.line.charAt( pos.start ) != LPAREN )
2872         {
2873             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
2874                 pos.lineNumber, pos.start ) );
2875         }
2876         else
2877         {
2878             pos.start++;
2879         }
2880         
2881         // Get rid of whites, comments end empty lines
2882         skipWhites( reader, pos, false );
2883         
2884         // Now, the OID. 
2885         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
2886         
2887         // Check that the OID is valid
2888         if ( !Oid.isOid( oid ) )
2889         {
2890             throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
2891         }
2892         
2893         AttributeType attributeType = new AttributeType( oid );
2894         boolean hasSup = false;
2895         boolean hasSyntax = false;
2896         int elementsSeen = 0;
2897         
2898         while ( true )
2899         {
2900             if ( startsWith( reader, pos, RPAREN ) )
2901             {
2902                 pos.start++;
2903                 break;
2904             }
2905             
2906             skipWhites( reader, pos, true );
2907 
2908             if ( startsWith( pos, NAME_STR ) )
2909             {
2910                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.NAME, pos );
2911                 
2912                 pos.start += NAME_STR.length();
2913                 
2914                 skipWhites( reader, pos, true );
2915 
2916                 attributeType.setNames( getQDescrs( reader, pos, STRICT ) );
2917             }
2918             else if ( startsWith( pos, DESC_STR ) )
2919             {
2920                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.DESC, pos );
2921 
2922                 pos.start += DESC_STR.length();
2923                 
2924                 skipWhites( reader, pos, true );
2925 
2926                 attributeType.setDescription( getQDString( reader, pos ) );
2927             }
2928             else if ( startsWith( pos, OBSOLETE_STR ) )
2929             {
2930                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.OBSOLETE, pos );
2931                 
2932                 pos.start += OBSOLETE_STR.length();
2933                 
2934                 attributeType.setObsolete( true );
2935             }
2936             else if ( startsWith( pos, SUP_STR ) )
2937             {
2938                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SUP, pos );
2939                 
2940                 pos.start += SUP_STR.length();
2941                 
2942                 skipWhites( reader, pos, true );
2943                 
2944                 String superiorOid = getOidStrict( pos );
2945 
2946                 attributeType.setSuperiorOid( superiorOid );
2947                 hasSup = true;
2948             }
2949             else if ( startsWith( pos, EQUALITY_STR ) )
2950             {
2951                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.EQUALITY, pos );
2952                 
2953                 pos.start += EQUALITY_STR.length();
2954                 
2955                 skipWhites( reader, pos, true );
2956                 
2957                 String equalityOid = getOidStrict( pos );
2958 
2959                 attributeType.setEqualityOid( equalityOid );
2960             }
2961             else if ( startsWith( pos, ORDERING_STR ) )
2962             {
2963                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.ORDERING, pos );
2964                 
2965                 pos.start += ORDERING_STR.length();
2966                 
2967                 skipWhites( reader, pos, true );
2968                 
2969                 String orderingOid = getOidStrict( pos );
2970 
2971                 attributeType.setOrderingOid( orderingOid );
2972             }
2973             else if ( startsWith( pos, SUBSTR_STR ) )
2974             {
2975                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SUBSTR, pos );
2976 
2977                 pos.start += SUBSTR_STR.length();
2978                 
2979                 skipWhites( reader, pos, true );
2980                 
2981                 String substrOid = getOidStrict( pos );
2982 
2983                 attributeType.setSubstringOid( substrOid );
2984             }
2985             else if ( startsWith( pos, SYNTAX_STR ) )
2986             {
2987                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SYNTAX, pos );
2988                 
2989                 pos.start += SYNTAX_STR.length();
2990                 
2991                 skipWhites( reader, pos, true );
2992                 
2993                 getNoidLenStrict( attributeType, pos );
2994 
2995                 hasSyntax = true;
2996             }
2997             else if ( startsWith( pos, SINGLE_VALUE_STR ) )
2998             {
2999                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SINGLE_VALUE, pos );
3000                 
3001                 pos.start += SINGLE_VALUE_STR.length();
3002                 
3003                 attributeType.setSingleValued( true );
3004             }
3005             else if ( startsWith( pos, COLLECTIVE_STR ) )
3006             {
3007                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.COLLECTIVE, pos );
3008                 
3009                 pos.start += COLLECTIVE_STR.length();
3010                 
3011                 attributeType.setCollective( true );
3012             }
3013             else if ( startsWith( pos, NO_USER_MODIFICATION_STR ) )
3014             {
3015                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.NO_USER_MODIFICATION, pos );
3016                 
3017                 pos.start += NO_USER_MODIFICATION_STR.length();
3018                 
3019                 attributeType.setUserModifiable( false );
3020             }
3021             else if ( startsWith( pos, USAGE_STR ) )
3022             {
3023                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.USAGE, pos );
3024                 
3025                 pos.start += USAGE_STR.length();
3026                 
3027                 skipWhites( reader, pos, true );
3028                 
3029                 UsageEnum usage = getUsageStrict( pos );
3030 
3031                 attributeType.setUsage( usage );
3032             }
3033             else if ( startsWith( pos, EXTENSION_PREFIX ) )
3034             {
3035                 processExtension( reader, pos, attributeType );
3036             }
3037             else if ( startsWith( reader, pos, RPAREN ) )
3038             {
3039                 pos.start++;
3040                 break;
3041             }
3042             else
3043             {
3044                 // This is an error
3045                 throw new LdapSchemaException( I18n.err( I18n.ERR_13798_AT_DESCRIPTION_INVALID, 
3046                     pos.lineNumber, pos.start ) );
3047             }
3048         }
3049         
3050         // Semantic checks
3051         if ( !hasSup && !hasSyntax )
3052         {
3053             throw new LdapSchemaException( I18n.err( I18n.ERR_13799_SYNTAX_OR_SUP_REQUIRED, 
3054                 pos.lineNumber, pos.start ) );
3055         }
3056 
3057         if ( attributeType.isCollective() && ( attributeType.getUsage() != UsageEnum.USER_APPLICATIONS ) )
3058         {
3059             throw new LdapSchemaException( I18n.err( I18n.ERR_13800_COLLECTIVE_REQUIRES_USER_APPLICATION, 
3060                 pos.lineNumber, pos.start ) );
3061         }
3062     
3063         // NO-USER-MODIFICATION requires an operational USAGE.
3064         if ( !attributeType.isUserModifiable() && ( attributeType.getUsage() == UsageEnum.USER_APPLICATIONS ) )
3065         {
3066             throw new LdapSchemaException( I18n.err( I18n.ERR_13801_NO_USER_MOD_REQUIRE_OPERATIONAL, 
3067                 pos.lineNumber, pos.start ) );
3068         }
3069         
3070         return attributeType;
3071     }
3072     
3073     
3074     /**
3075      * Production for matching attribute type descriptions. It is fault-tolerant
3076      * against element ordering. It's relaxed.
3077      *
3078      * <pre>
3079      * AttributeTypeDescription = LPAREN WSP
3080      *     numericoid                    ; object identifier
3081      *     [ SP "NAME" SP qdescrs ]      ; short names (descriptors)
3082      *     [ SP "DESC" SP qdstring ]     ; description
3083      *     [ SP "OBSOLETE" ]             ; not active
3084      *     [ SP "SUP" SP oid ]           ; supertype
3085      *     [ SP "EQUALITY" SP oid ]      ; equality matching rule
3086      *     [ SP "ORDERING" SP oid ]      ; ordering matching rule
3087      *     [ SP "SUBSTR" SP oid ]        ; substrings matching rule
3088      *     [ SP "SYNTAX" SP noidlen ]    ; value syntax
3089      *     [ SP "SINGLE-VALUE" ]         ; single-value
3090      *     [ SP "COLLECTIVE" ]           ; collective
3091      *     [ SP "NO-USER-MODIFICATION" ] ; not user modifiable
3092      *     [ SP "USAGE" SP usage ]       ; usage
3093      *     extensions WSP RPAREN         ; extensions
3094      * 
3095      * usage = "userApplications"     /  ; user
3096      *         "directoryOperation"   /  ; directory operational
3097      *         "distributedOperation" /  ; DSA-shared operational
3098      *         "dSAOperation"            ; DSA-specific operational     
3099      * 
3100      * extensions = *( SP xstring SP qdstrings )
3101      * xstring = "X" HYPHEN 1*( ALPHA / HYPHEN / USCORE ) 
3102      * </pre>
3103      * 
3104      * @param reader The stream reader
3105      * @param pos The position in the Schema
3106      * @param objectIdentifierMacros The set of existing Macros
3107      * @return An instance of AttributeType
3108      * @throws IOException If the stream can't be read
3109      * @throws LdapSchemaException If the schema is wrong
3110      */
3111     private static AttributeType parseAttributeTypeRelaxed( Reader reader, PosSchema pos,
3112         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException
3113     {
3114         // Get rid of whites, comments end empty lines
3115         skipWhites( reader, pos, false );
3116         
3117         // we must have a '('
3118         if ( pos.line.charAt( pos.start ) != LPAREN )
3119         {
3120             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
3121                 pos.lineNumber, pos.start ) );
3122         }
3123         else
3124         {
3125             pos.start++;
3126         }
3127         
3128         // Get rid of whites, comments end empty lines
3129         skipWhites( reader, pos, false );
3130         
3131         // Now, the OID. 
3132         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
3133         
3134         AttributeType attributeType = new AttributeType( oid );
3135         int elementsSeen = 0;
3136         
3137         while ( true )
3138         {
3139             if ( startsWith( reader, pos, RPAREN ) )
3140             {
3141                 pos.start++;
3142                 break;
3143             }
3144             
3145             // Make whitespace non-mandatory here.
3146             // E.g. OpenDJ is missing the the space in some schema definitions.
3147             skipWhites( reader, pos, false );
3148 
3149             if ( startsWith( pos, NAME_STR ) )
3150             {
3151                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.NAME, pos );
3152                 
3153                 pos.start += NAME_STR.length();
3154                 
3155                 skipWhites( reader, pos, true );
3156 
3157                 attributeType.setNames( getQDescrs( reader, pos, RELAXED ) );
3158             }
3159             else if ( startsWith( pos, DESC_STR ) )
3160             {
3161                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.DESC, pos );
3162 
3163                 pos.start += DESC_STR.length();
3164                 
3165                 skipWhites( reader, pos, true );
3166 
3167                 attributeType.setDescription( getQDString( reader, pos ) );
3168             }
3169             else if ( startsWith( pos, OBSOLETE_STR ) )
3170             {
3171                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.OBSOLETE, pos );
3172                 
3173                 pos.start += OBSOLETE_STR.length();
3174                 
3175                 attributeType.setObsolete( true );
3176             }
3177             else if ( startsWith( pos, SUP_STR ) )
3178             {
3179                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SUP, pos );
3180                 
3181                 pos.start += SUP_STR.length();
3182                 
3183                 skipWhites( reader, pos, true );
3184                 
3185                 String superiorOid = getOidRelaxed( pos, false );
3186 
3187                 attributeType.setSuperiorOid( superiorOid );
3188             }
3189             else if ( startsWith( pos, EQUALITY_STR ) )
3190             {
3191                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.EQUALITY, pos );
3192                 
3193                 pos.start += EQUALITY_STR.length();
3194                 
3195                 skipWhites( reader, pos, true );
3196                 
3197                 String equalityOid = getOidRelaxed( pos, false );
3198 
3199                 attributeType.setEqualityOid( equalityOid );
3200             }
3201             else if ( startsWith( pos, ORDERING_STR ) )
3202             {
3203                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.ORDERING, pos );
3204                 
3205                 pos.start += ORDERING_STR.length();
3206                 
3207                 skipWhites( reader, pos, true );
3208                 
3209                 String orderingOid = getOidRelaxed( pos, false );
3210 
3211                 attributeType.setOrderingOid( orderingOid );
3212             }
3213             else if ( startsWith( pos, SUBSTR_STR ) )
3214             {
3215                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SUBSTR, pos );
3216 
3217                 pos.start += SUBSTR_STR.length();
3218                 
3219                 skipWhites( reader, pos, true );
3220                 
3221                 String substrOid = getOidRelaxed( pos, false );
3222 
3223                 attributeType.setSubstringOid( substrOid );
3224             }
3225             else if ( startsWith( pos, SYNTAX_STR ) )
3226             {
3227                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SYNTAX, pos );
3228                 
3229                 pos.start += SYNTAX_STR.length();
3230                 
3231                 skipWhites( reader, pos, true );
3232                 
3233                 getNoidLenRelaxed( attributeType, pos );
3234             }
3235             else if ( startsWith( pos, SINGLE_VALUE_STR ) )
3236             {
3237                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SINGLE_VALUE, pos );
3238                 
3239                 pos.start += SINGLE_VALUE_STR.length();
3240                 
3241                 attributeType.setSingleValued( true );
3242             }
3243             else if ( startsWith( pos, COLLECTIVE_STR ) )
3244             {
3245                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.COLLECTIVE, pos );
3246                 
3247                 pos.start += COLLECTIVE_STR.length();
3248                 
3249                 attributeType.setCollective( true );
3250             }
3251             else if ( startsWith( pos, NO_USER_MODIFICATION_STR ) )
3252             {
3253                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.NO_USER_MODIFICATION, pos );
3254                 
3255                 pos.start += NO_USER_MODIFICATION_STR.length();
3256                 
3257                 attributeType.setUserModifiable( false );
3258             }
3259             else if ( startsWith( pos, USAGE_STR ) )
3260             {
3261                 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.USAGE, pos );
3262                 
3263                 pos.start += USAGE_STR.length();
3264                 
3265                 skipWhites( reader, pos, true );
3266                 
3267                 UsageEnum usage = getUsageRelaxed( pos );
3268 
3269                 attributeType.setUsage( usage );
3270             }
3271             else if ( startsWith( pos, EXTENSION_PREFIX ) )
3272             {
3273                 processExtension( reader, pos, attributeType );
3274             }
3275             else if ( startsWith( reader, pos, RPAREN ) )
3276             {
3277                 pos.start++;
3278                 break;
3279             }
3280             else
3281             {
3282                 // This is an error
3283                 throw new LdapSchemaException( I18n.err( I18n.ERR_13798_AT_DESCRIPTION_INVALID, 
3284                     pos.lineNumber, pos.start ) );
3285             }
3286         }
3287         
3288         return attributeType;
3289     }
3290 
3291     
3292     /**
3293      * Production for matching DitContentRule descriptions. It is fault-tolerant
3294      * against element ordering.
3295      *
3296      * <pre>
3297      * DITContentRuleDescription = LPAREN WSP
3298      *    numericoid                 ; object identifier
3299      *    [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
3300      *    [ SP "DESC" SP qdstring ]  ; description
3301      *    [ SP "OBSOLETE" ]          ; not active
3302      *    [ SP "AUX" SP oids ]       ; auxiliary object classes
3303      *    [ SP "MUST" SP oids ]      ; attribute types
3304      *    [ SP "MAY" SP oids ]       ; attribute types
3305      *    [ SP "NOT" SP oids ]       ; attribute types
3306      *    extensions WSP RPAREN      ; extensions
3307      * </pre>
3308      * 
3309      * @param ditContentRuleDescription The String containing the DitContentRuleDescription
3310      * @return An instance of ditContentRule
3311      * @throws ParseException If the element was invalid
3312      */
3313     public DitContentRule parseDitContentRule( String ditContentRuleDescription ) throws ParseException
3314     {
3315         if ( ( ditContentRuleDescription == null ) || Strings.isEmpty( ditContentRuleDescription.trim() ) )
3316         {
3317             throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
3318         }
3319         
3320         try ( Reader reader = new BufferedReader( new StringReader( ditContentRuleDescription ) ) )
3321         {
3322             PosSchema pos = new PosSchema();
3323 
3324             if ( isQuirksModeEnabled )
3325             {
3326                 return parseDitContentRuleRelaxed( reader, pos, objectIdentifierMacros );
3327             }
3328             else
3329             {
3330                 return parseDitContentRuleStrict( reader, pos, objectIdentifierMacros );
3331             }
3332         }
3333         catch ( IOException | LdapSchemaException e )
3334         {
3335             throw new ParseException( e.getMessage(), 0 );
3336         }
3337     }
3338 
3339     
3340     /**
3341      * Production for DitContentRule descriptions. It is fault-tolerant
3342      * against element ordering.
3343      *
3344      * <pre>
3345      * DITContentRuleDescription = LPAREN WSP
3346      *    numericoid                 ; object identifier
3347      *    [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
3348      *    [ SP "DESC" SP qdstring ]  ; description
3349      *    [ SP "OBSOLETE" ]          ; not active
3350      *    [ SP "AUX" SP oids ]       ; auxiliary object classes
3351      *    [ SP "MUST" SP oids ]      ; attribute types
3352      *    [ SP "MAY" SP oids ]       ; attribute types
3353      *    [ SP "NOT" SP oids ]       ; attribute types
3354      *    extensions WSP RPAREN      ; extensions
3355      * </pre>
3356      * 
3357      * @param reader The stream reader
3358      * @param pos The position in the Schema
3359      * @param objectIdentifierMacros The set of existing Macros
3360      * @return An instance of DitContentRule
3361      * @throws LdapSchemaException If the schema is wrong
3362      * @throws IOException If the stream can't be read
3363      */
3364     private static DitContentRule parseDitContentRuleStrict( Reader reader, PosSchema pos,
3365         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException
3366     {
3367         // Get rid of whites, comments end empty lines
3368         skipWhites( reader, pos, false );
3369         
3370         // we must have a '('
3371         if ( pos.line.charAt( pos.start ) != LPAREN )
3372         {
3373             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
3374                 pos.lineNumber, pos.start ) );
3375         }
3376         else
3377         {
3378             pos.start++;
3379         }
3380         
3381         // Get rid of whites, comments end empty lines
3382         skipWhites( reader, pos, false );
3383         
3384         // Now, the OID. 
3385         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
3386         
3387         // Check that the OID is valid
3388         if ( !Oid.isOid( oid ) )
3389         {
3390             throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
3391         }
3392         
3393         DitContentRule ditContentRule = new DitContentRule( oid );
3394         int elementsSeen = 0;
3395         
3396         while ( true )
3397         {
3398             if ( startsWith( reader, pos, RPAREN ) )
3399             {
3400                 pos.start++;
3401                 break;
3402             }
3403             
3404             skipWhites( reader, pos, true );
3405             
3406             if ( startsWith( pos, NAME_STR ) )
3407             {
3408                 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.NAME, pos );
3409 
3410                 pos.start += NAME_STR.length();
3411                 
3412                 skipWhites( reader, pos, true );
3413 
3414                 ditContentRule.setNames( getQDescrs( reader, pos, STRICT ) );
3415             }
3416             else if ( startsWith( pos, DESC_STR ) )
3417             {
3418                 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.DESC, pos );
3419 
3420                 pos.start += DESC_STR.length();
3421                 
3422                 skipWhites( reader, pos, true );
3423 
3424                 ditContentRule.setDescription( getQDString( reader, pos ) );
3425             }
3426             else if ( startsWith( pos, OBSOLETE_STR ) )
3427             {
3428                 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.OBSOLETE, pos );
3429 
3430                 pos.start += OBSOLETE_STR.length();
3431                 
3432                 ditContentRule.setObsolete( true );
3433             }
3434             else if ( startsWith( pos, AUX_STR ) )
3435             {
3436                 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.AUX, pos );
3437 
3438                 pos.start += AUX_STR.length();
3439                 
3440                 skipWhites( reader, pos, true );
3441                 
3442                 List<String> aux = getOidsStrict( reader, pos );
3443                 
3444                 ditContentRule.setAuxObjectClassOids( aux );
3445             }
3446             else if ( startsWith( pos, MUST_STR ) )
3447             {
3448                 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.MUST, pos );
3449 
3450                 pos.start += MUST_STR.length();
3451                 
3452                 skipWhites( reader, pos, true );
3453                 
3454                 List<String> must = getOidsStrict( reader, pos );
3455                 
3456                 ditContentRule.setMustAttributeTypeOids( must );
3457             }
3458             else if ( startsWith( pos, MAY_STR ) )
3459             {
3460                 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.MAY, pos );
3461 
3462                 pos.start += MAY_STR.length();
3463                 
3464                 skipWhites( reader, pos, true );
3465                 
3466                 List<String> may = getOidsStrict( reader, pos );
3467                 
3468                 ditContentRule.setMayAttributeTypeOids( may );
3469             }
3470             else if ( startsWith( pos, NOT_STR ) )
3471             {
3472                 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.NOT, pos );
3473 
3474                 pos.start += NOT_STR.length();
3475                 
3476                 skipWhites( reader, pos, true );
3477                 
3478                 List<String> not = getOidsStrict( reader, pos );
3479                 
3480                 ditContentRule.setNotAttributeTypeOids( not );
3481             }
3482             else if ( startsWith( pos, EXTENSION_PREFIX ) )
3483             {
3484                 processExtension( reader, pos, ditContentRule );
3485             }
3486             else if ( startsWith( reader, pos, RPAREN ) )
3487             {
3488                 pos.start++;
3489 
3490                 break;
3491             }
3492             else
3493             {
3494                 // This is an error
3495                 throw new LdapSchemaException( I18n.err( I18n.ERR_13809_DCR_DESCRIPTION_INVALID, 
3496                     pos.lineNumber, pos.start ) );
3497             }
3498         }
3499         
3500         return ditContentRule;
3501     }
3502 
3503     
3504     /**
3505      * Production for DitContentRule descriptions. It is fault-tolerant
3506      * against element ordering.
3507      *
3508      * <pre>
3509      * DITContentRuleDescription = LPAREN WSP
3510      *    numericoid                 ; object identifier
3511      *    [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
3512      *    [ SP "DESC" SP qdstring ]  ; description
3513      *    [ SP "OBSOLETE" ]          ; not active
3514      *    [ SP "AUX" SP oids ]       ; auxiliary object classes
3515      *    [ SP "MUST" SP oids ]      ; attribute types
3516      *    [ SP "MAY" SP oids ]       ; attribute types
3517      *    [ SP "NOT" SP oids ]       ; attribute types
3518      *    extensions WSP RPAREN      ; extensions
3519      * </pre>
3520      * 
3521      * @param reader The stream reader
3522      * @param pos The position in the Schema
3523      * @param objectIdentifierMacros The set of existing Macros
3524      * @return An instance of DitContentRule
3525      * @throws LdapSchemaException If the schema is wrong
3526      * @throws IOException If the stream can't be read
3527      */
3528     private static DitContentRule parseDitContentRuleRelaxed( Reader reader, PosSchema pos,
3529         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException
3530     {
3531         // Get rid of whites, comments end empty lines
3532         skipWhites( reader, pos, false );
3533         
3534         // we must have a '('
3535         if ( pos.line.charAt( pos.start ) != LPAREN )
3536         {
3537             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
3538                 pos.lineNumber, pos.start ) );
3539         }
3540         else
3541         {
3542             pos.start++;
3543         }
3544         
3545         // Get rid of whites, comments end empty lines
3546         skipWhites( reader, pos, false );
3547         
3548         // Now, the OID. 
3549         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
3550         // Now, the OID. 
3551         
3552         DitContentRule ditContentRule = new DitContentRule( oid );
3553         int elementsSeen = 0;
3554         
3555         while ( true )
3556         {
3557             if ( startsWith( reader, pos, RPAREN ) )
3558             {
3559                 pos.start++;
3560                 break;
3561             }
3562             
3563             skipWhites( reader, pos, true );
3564 
3565             if ( startsWith( pos, NAME_STR ) )
3566             {
3567                 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.NAME, pos );
3568 
3569                 pos.start += NAME_STR.length();
3570                 
3571                 skipWhites( reader, pos, true );
3572 
3573                 ditContentRule.setNames( getQDescrs( reader, pos, RELAXED ) );
3574             }
3575             else if ( startsWith( pos, DESC_STR ) )
3576             {
3577                 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.DESC, pos );
3578 
3579                 pos.start += DESC_STR.length();
3580                 
3581                 skipWhites( reader, pos, true );
3582 
3583                 ditContentRule.setDescription( getQDString( reader, pos ) );
3584             }
3585             else if ( startsWith( pos, OBSOLETE_STR ) )
3586             {
3587                 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.OBSOLETE, pos );
3588 
3589                 pos.start += OBSOLETE_STR.length();
3590                 
3591                 ditContentRule.setObsolete( true );
3592             }
3593             else if ( startsWith( pos, AUX_STR ) )
3594             {
3595                 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.AUX, pos );
3596 
3597                 pos.start += AUX_STR.length();
3598                 
3599                 skipWhites( reader, pos, true );
3600                 
3601                 List<String> aux = getOidsRelaxed( reader, pos );
3602                 
3603                 ditContentRule.setAuxObjectClassOids( aux );
3604             }
3605             else if ( startsWith( pos, MUST_STR ) )
3606             {
3607                 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.MUST, pos );
3608 
3609                 pos.start += MUST_STR.length();
3610                 
3611                 skipWhites( reader, pos, true );
3612                 
3613                 List<String> must = getOidsRelaxed( reader, pos );
3614                 
3615                 ditContentRule.setMustAttributeTypeOids( must );
3616             }
3617             else if ( startsWith( pos, MAY_STR ) )
3618             {
3619                 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.MAY, pos );
3620 
3621                 pos.start += MAY_STR.length();
3622                 
3623                 skipWhites( reader, pos, true );
3624                 
3625                 List<String> may = getOidsRelaxed( reader, pos );
3626                 
3627                 ditContentRule.setMayAttributeTypeOids( may );
3628             }
3629             else if ( startsWith( pos, NOT_STR ) )
3630             {
3631                 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.NOT, pos );
3632 
3633                 pos.start += NOT_STR.length();
3634                 
3635                 skipWhites( reader, pos, true );
3636                 
3637                 List<String> not = getOidsRelaxed( reader, pos );
3638                 
3639                 ditContentRule.setNotAttributeTypeOids( not );
3640             }
3641             else if ( startsWith( pos, EXTENSION_PREFIX ) )
3642             {
3643                 processExtension( reader, pos, ditContentRule );
3644             }
3645             else if ( startsWith( reader, pos, RPAREN ) )
3646             {
3647                 pos.start++;
3648 
3649                 break;
3650             }
3651             else
3652             {
3653                 // This is an error
3654                 throw new LdapSchemaException( I18n.err( I18n.ERR_13809_DCR_DESCRIPTION_INVALID, 
3655                     pos.lineNumber, pos.start ) );
3656             }
3657         }
3658         
3659         return ditContentRule;
3660     }
3661 
3662     
3663     /**
3664      * Production for matching DitStructureRule descriptions. It is fault-tolerant
3665      * against element ordering.
3666      *
3667      * <pre>
3668      * DITStructureRuleDescription = LPAREN WSP
3669      *   ruleid                     ; rule identifier
3670      *   [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
3671      *   [ SP "DESC" SP qdstring ]  ; description
3672      *   [ SP "OBSOLETE" ]          ; not active
3673      *   SP "FORM" SP oid           ; NameForm
3674      *   [ SP "SUP" ruleids ]       ; superior rules
3675      *   extensions WSP RPAREN      ; extensions
3676      *
3677      * ruleids = ruleid / ( LPAREN WSP ruleidlist WSP RPAREN )
3678      * ruleidlist = ruleid *( SP ruleid )
3679      * ruleid = number
3680      * </pre>
3681      * 
3682      * @param ditStructureRuleDescription The String containing the DitStructureRuleDescription
3683      * @return An instance of DitStructureRule
3684      * @throws ParseException If the element was invalid
3685      */
3686     public DitStructureRule parseDitStructureRule( String ditStructureRuleDescription ) throws ParseException
3687     {
3688         if ( ( ditStructureRuleDescription == null ) || Strings.isEmpty( ditStructureRuleDescription.trim() ) )
3689         {
3690             throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
3691         }
3692         
3693         try ( Reader reader = new BufferedReader( new StringReader( ditStructureRuleDescription ) ) )
3694         {
3695             PosSchema pos = new PosSchema();
3696 
3697             if ( isQuirksModeEnabled )
3698             {
3699                 return parseDitStructureRuleRelaxed( reader, pos, objectIdentifierMacros );
3700             }
3701             else
3702             {
3703                 return parseDitStructureRuleStrict( reader, pos );
3704             }
3705         }
3706         catch ( IOException | LdapSchemaException e )
3707         {
3708             throw new ParseException( e.getMessage(), 0 );
3709         }
3710     }
3711 
3712     
3713     /**
3714      * Production for DitStructureRule descriptions. It is fault-tolerant
3715      * against element ordering.
3716      *
3717      * <pre>
3718      * DITStructureRuleDescription = LPAREN WSP
3719      *   ruleid                     ; rule identifier
3720      *   [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
3721      *   [ SP "DESC" SP qdstring ]  ; description
3722      *   [ SP "OBSOLETE" ]          ; not active
3723      *   SP "FORM" SP oid           ; NameForm
3724      *   [ SP "SUP" ruleids ]       ; superior rules
3725      *   extensions WSP RPAREN      ; extensions
3726      *
3727      * ruleids = ruleid / ( LPAREN WSP ruleidlist WSP RPAREN )
3728      * ruleidlist = ruleid *( SP ruleid )
3729      * ruleid = number
3730      * </pre>
3731      * 
3732      * @param reader The stream reader
3733      * @param pos The position in the Schema
3734      * @return An instance of DitStructureRule
3735      * @throws LdapSchemaException If the schema is wrong
3736      * @throws IOException If the stream can't be read
3737      */
3738     private static DitStructureRule parseDitStructureRuleStrict( Reader reader, PosSchema pos ) 
3739         throws IOException, LdapSchemaException
3740     {
3741         // Get rid of whites, comments end empty lines
3742         skipWhites( reader, pos, false );
3743         
3744         // we must have a '('
3745         if ( pos.line.charAt( pos.start ) != LPAREN )
3746         {
3747             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
3748                 pos.lineNumber, pos.start ) );
3749         }
3750         else
3751         {
3752             pos.start++;
3753         }
3754         
3755         // Get rid of whites, comments end empty lines
3756         skipWhites( reader, pos, false );
3757         
3758         // Now, the ruleID. 
3759         int ruleId = getRuleId( pos );
3760         
3761         DitStructureRule ditStructureRule = new DitStructureRule( ruleId );
3762         int elementsSeen = 0;
3763         boolean hasForm = false;
3764         
3765         while ( true )
3766         {
3767             if ( startsWith( reader, pos, RPAREN ) )
3768             {
3769                 pos.start++;
3770                 break;
3771             }
3772             
3773             skipWhites( reader, pos, true );
3774             
3775             if ( startsWith( pos, NAME_STR ) )
3776             {
3777                 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.NAME, pos );
3778 
3779                 pos.start += NAME_STR.length();
3780                 
3781                 skipWhites( reader, pos, true );
3782 
3783                 ditStructureRule.setNames( getQDescrs( reader, pos, STRICT ) );
3784             }
3785             else if ( startsWith( pos, DESC_STR ) )
3786             {
3787                 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.DESC, pos );
3788 
3789                 pos.start += DESC_STR.length();
3790                 
3791                 skipWhites( reader, pos, true );
3792 
3793                 ditStructureRule.setDescription( getQDString( reader, pos ) );
3794             }
3795             else if ( startsWith( pos, OBSOLETE_STR ) )
3796             {
3797                 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.OBSOLETE, pos );
3798 
3799                 pos.start += OBSOLETE_STR.length();
3800                 
3801                 ditStructureRule.setObsolete( true );
3802             }
3803             else if ( startsWith( pos, FORM_STR ) )
3804             {
3805                 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.FORM, pos );
3806 
3807                 pos.start += FORM_STR.length();
3808                 
3809                 skipWhites( reader, pos, true );
3810                 
3811                 String form = getOidStrict( pos );
3812                 
3813                 ditStructureRule.setForm( form );
3814                 hasForm = true;
3815             }
3816             else if ( startsWith( pos, SUP_STR ) )
3817             {
3818                 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.SUP, pos );
3819 
3820                 pos.start += SUP_STR.length();
3821                 
3822                 skipWhites( reader, pos, true );
3823                 
3824                 List<Integer> superRules = getRuleIds( reader, pos );
3825                 
3826                 ditStructureRule.setSuperRules( superRules );
3827             }
3828             else if ( startsWith( pos, EXTENSION_PREFIX ) )
3829             {
3830                 processExtension( reader, pos, ditStructureRule );
3831             }
3832             else if ( startsWith( reader, pos, RPAREN ) )
3833             {
3834                 pos.start++;
3835                 break;
3836             }
3837             else
3838             {
3839                 // This is an error
3840                 throw new LdapSchemaException( I18n.err( I18n.ERR_13809_DCR_DESCRIPTION_INVALID, 
3841                     pos.lineNumber, pos.start ) );
3842             }
3843         }
3844         
3845         // Semantic checks
3846         if ( !hasForm )
3847         {
3848             throw new LdapSchemaException( I18n.err( I18n.ERR_13812_FORM_REQUIRED, 
3849                 pos.lineNumber, pos.start ) );
3850         }
3851 
3852         return ditStructureRule;
3853     }
3854 
3855     
3856     /**
3857      * Production for DitStructureRule descriptions. It is fault-tolerant
3858      * against element ordering.
3859      *
3860      * <pre>
3861      * DITStructureRuleDescription = LPAREN WSP
3862      *   ruleid                     ; rule identifier
3863      *   [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
3864      *   [ SP "DESC" SP qdstring ]  ; description
3865      *   [ SP "OBSOLETE" ]          ; not active
3866      *   SP "FORM" SP oid           ; NameForm
3867      *   [ SP "SUP" ruleids ]       ; superior rules
3868      *   extensions WSP RPAREN      ; extensions
3869      *
3870      * ruleids = ruleid / ( LPAREN WSP ruleidlist WSP RPAREN )
3871      * ruleidlist = ruleid *( SP ruleid )
3872      * ruleid = number
3873      * </pre>
3874      * 
3875      * @param reader The stream reader
3876      * @param pos The position in the Schema
3877      * @param objectIdentifierMacros The set of existing Macros
3878      * @return An instance of DitStructureRule
3879      * @throws LdapSchemaException If the schema is wrong
3880      * @throws IOException If the stream can't be read
3881      */
3882     private static DitStructureRule parseDitStructureRuleRelaxed( Reader reader, PosSchema pos,
3883         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 
3884             throws IOException, LdapSchemaException
3885     {
3886         // Get rid of whites, comments end empty lines
3887         skipWhites( reader, pos, false );
3888         
3889         // we must have a '('
3890         if ( pos.line.charAt( pos.start ) != LPAREN )
3891         {
3892             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
3893                 pos.lineNumber, pos.start ) );
3894         }
3895         else
3896         {
3897             pos.start++;
3898         }
3899         
3900         // Get rid of whites, comments end empty lines
3901         skipWhites( reader, pos, false );
3902         
3903         // Now, the ruleID. 
3904         int ruleId = getRuleId( pos );
3905         
3906         DitStructureRule ditStructureRule = new DitStructureRule( ruleId );
3907         int elementsSeen = 0;
3908         boolean hasForm = false;
3909         
3910         while ( true )
3911         {
3912             if ( startsWith( reader, pos, RPAREN ) )
3913             {
3914                 pos.start++;
3915                 break;
3916             }
3917             
3918             skipWhites( reader, pos, true );
3919             
3920             if ( startsWith( pos, NAME_STR ) )
3921             {
3922                 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.NAME, pos );
3923 
3924                 pos.start += NAME_STR.length();
3925                 
3926                 skipWhites( reader, pos, true );
3927 
3928                 ditStructureRule.setNames( getQDescrs( reader, pos, RELAXED ) );
3929             }
3930             else if ( startsWith( pos, DESC_STR ) )
3931             {
3932                 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.DESC, pos );
3933 
3934                 pos.start += DESC_STR.length();
3935                 
3936                 skipWhites( reader, pos, true );
3937 
3938                 ditStructureRule.setDescription( getQDString( reader, pos ) );
3939             }
3940             else if ( startsWith( pos, OBSOLETE_STR ) )
3941             {
3942                 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.OBSOLETE, pos );
3943 
3944                 pos.start += OBSOLETE_STR.length();
3945                 
3946                 ditStructureRule.setObsolete( true );
3947             }
3948             else if ( startsWith( pos, FORM_STR ) )
3949             {
3950                 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.FORM, pos );
3951 
3952                 pos.start += FORM_STR.length();
3953                 
3954                 skipWhites( reader, pos, true );
3955                 
3956                 String form = getOidRelaxed( pos, UN_QUOTED );
3957                 
3958                 ditStructureRule.setForm( form );
3959                 hasForm = true;
3960             }
3961             else if ( startsWith( pos, SUP_STR ) )
3962             {
3963                 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.SUP, pos );
3964 
3965                 pos.start += SUP_STR.length();
3966                 
3967                 skipWhites( reader, pos, true );
3968                 
3969                 List<Integer> superRules = getRuleIds( reader, pos );
3970                 
3971                 ditStructureRule.setSuperRules( superRules );
3972             }
3973             else if ( startsWith( pos, EXTENSION_PREFIX ) )
3974             {
3975                 processExtension( reader, pos, ditStructureRule );
3976             }
3977             else if ( startsWith( reader, pos, RPAREN ) )
3978             {
3979                 pos.start++;
3980                 break;
3981             }
3982             else
3983             {
3984                 // This is an error
3985                 throw new LdapSchemaException( I18n.err( I18n.ERR_13809_DCR_DESCRIPTION_INVALID, 
3986                     pos.lineNumber, pos.start ) );
3987             }
3988         }
3989 
3990         if ( !hasForm )
3991         {
3992             throw new LdapSchemaException( I18n.err( I18n.ERR_13812_FORM_REQUIRED, 
3993                 pos.lineNumber, pos.start ) );
3994         }
3995 
3996         return ditStructureRule;
3997     }
3998 
3999     
4000     /**
4001      * Production for LdapComparator descriptions. It is fault-tolerant
4002      * against element ordering.
4003      *
4004      * <pre>
4005      * LdapComparatorDescription = LPAREN WSP
4006      *       numericoid                           ; object identifier
4007      *       [ SP "DESC" SP qdstring ]            ; description
4008      *       SP "FQCN" SP fqcn                    ; fully qualified class name
4009      *       [ SP "BYTECODE" SP base64 ]          ; optional base64 encoded bytecode
4010      *       extensions WSP RPAREN                ; extensions
4011      * 
4012      * base64          = *(4base64-char)
4013      * base64-char     = ALPHA / DIGIT / "+" / "/"
4014      * fqcn = fqcnComponent 1*( DOT fqcnComponent )
4015      * fqcnComponent = ???
4016      * </pre>
4017      * 
4018      * @param ldapComparatorDescription The String containing the LdapComparatorDescription
4019      * @return An instance of LdapComparatorDescription
4020      * @throws ParseException If the element was invalid
4021      */
4022     public LdapComparatorDescription parseLdapComparator( String ldapComparatorDescription ) throws ParseException
4023     {
4024         if ( ( ldapComparatorDescription == null ) || Strings.isEmpty( ldapComparatorDescription.trim() ) )
4025         {
4026             throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
4027         }
4028         
4029         try ( Reader reader = new BufferedReader( new StringReader( ldapComparatorDescription ) ) )
4030         {
4031             PosSchema pos = new PosSchema();
4032 
4033             if ( isQuirksModeEnabled )
4034             {
4035                 return parseLdapComparatorRelaxed( reader, pos, objectIdentifierMacros );
4036             }
4037             else
4038             {
4039                 return parseLdapComparatorStrict( reader, pos, objectIdentifierMacros );
4040             }
4041         }
4042         catch ( IOException | LdapSchemaException e )
4043         {
4044             throw new ParseException( e.getMessage(), 0 );
4045         }
4046     }
4047 
4048     
4049     /**
4050      * Production for LdapComparator descriptions. It is fault-tolerant
4051      * against element ordering.
4052      *
4053      * <pre>
4054      * LdapComparatorDescription = LPAREN WSP
4055      *       numericoid                           ; object identifier
4056      *       [ SP "DESC" SP qdstring ]            ; description
4057      *       SP "FQCN" SP fqcn                    ; fully qualified class name
4058      *       [ SP "BYTECODE" SP base64 ]          ; optional base64 encoded bytecode
4059      *       extensions WSP RPAREN                ; extensions
4060      * 
4061      * base64          = *(4base64-char)
4062      * base64-char     = ALPHA / DIGIT / "+" / "/"
4063      * fqcn = fqcnComponent 1*( DOT fqcnComponent )
4064      * fqcnComponent = ???
4065      * </pre>
4066      * 
4067      * @param reader The stream reader
4068      * @param pos The position in the Schema
4069      * @param objectIdentifierMacros The set of existing Macros
4070      * @return An instance of LdapComparatorDescription
4071      * @throws LdapSchemaException If the schema is wrong
4072      * @throws IOException If the stream can't be read
4073      */
4074     private static LdapComparatorDescription parseLdapComparatorStrict( Reader reader, PosSchema pos,
4075         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException
4076     {
4077         // Get rid of whites, comments end empty lines
4078         skipWhites( reader, pos, false );
4079         
4080         // we must have a '('
4081         if ( pos.line.charAt( pos.start ) != LPAREN )
4082         {
4083             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
4084                 pos.lineNumber, pos.start ) );
4085         }
4086         else
4087         {
4088             pos.start++;
4089         }
4090         
4091         // Get rid of whites, comments end empty lines
4092         skipWhites( reader, pos, false );
4093         
4094         // Now, the OID. 
4095         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
4096         
4097         // Check that the OID is valid
4098         if ( !Oid.isOid( oid ) )
4099         {
4100             throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
4101         }
4102         
4103         LdapComparatorDescription ldapComparator = new LdapComparatorDescription( oid );
4104         int elementsSeen = 0;
4105         boolean hasFqcn = false;
4106         boolean hasByteCode = false;
4107         
4108         while ( true )
4109         {
4110             if ( startsWith( reader, pos, RPAREN ) )
4111             {
4112                 pos.start++;
4113                 break;
4114             }
4115             
4116             skipWhites( reader, pos, true );
4117             
4118             if ( startsWith( pos, DESC_STR ) )
4119             {
4120                 elementsSeen = checkElement( elementsSeen, LdapComparatorElements.DESC, pos );
4121 
4122                 pos.start += DESC_STR.length();
4123                 
4124                 skipWhites( reader, pos, true );
4125 
4126                 ldapComparator.setDescription( getQDString( reader, pos ) );
4127             }
4128             else if ( startsWith( pos, FQCN_STR ) )
4129             {
4130                 elementsSeen = checkElement( elementsSeen, LdapComparatorElements.FQCN, pos );
4131 
4132                 pos.start += FQCN_STR.length();
4133                 
4134                 skipWhites( reader, pos, true );
4135 
4136                 String fqcn = getFqcn( pos );
4137                 ldapComparator.setFqcn( fqcn );
4138                 hasFqcn = true;
4139             }
4140             else if ( startsWith( pos, BYTECODE_STR ) )
4141             {
4142                 elementsSeen = checkElement( elementsSeen, LdapComparatorElements.BYTECODE, pos );
4143 
4144                 pos.start += BYTECODE_STR.length();
4145                 
4146                 skipWhites( reader, pos, true );
4147                 
4148                 String byteCode = getByteCode( pos );
4149                 ldapComparator.setBytecode( byteCode );
4150                 hasByteCode = true;
4151             }
4152             else if ( startsWith( pos, EXTENSION_PREFIX ) )
4153             {
4154                 processExtension( reader, pos, ldapComparator );
4155             }
4156             else if ( startsWith( reader, pos, RPAREN ) )
4157             {
4158                 pos.start++;
4159                 break;
4160             }
4161             else
4162             {
4163                 // This is an error
4164                 throw new LdapSchemaException( I18n.err( I18n.ERR_13825_COMP_DESCRIPTION_INVALID, 
4165                     pos.lineNumber, pos.start ) );
4166             }
4167         }
4168         
4169         // Semantic checks
4170         if ( !hasFqcn )
4171         {
4172             throw new LdapSchemaException( I18n.err( I18n.ERR_13819_FQCN_REQUIRED, 
4173                 pos.lineNumber, pos.start ) );
4174         }
4175 
4176         if ( ( hasByteCode ) && ( ldapComparator.getBytecode().length() % 4 != 0 ) )
4177         {
4178             throw new LdapSchemaException( I18n.err( I18n.ERR_13820_BYTE_CODE_REQUIRED, 
4179                 pos.lineNumber, pos.start ) );
4180         }
4181 
4182         return ldapComparator;
4183     }
4184 
4185     
4186     /**
4187      * Production for LdapComparator descriptions. It is fault-tolerant
4188      * against element ordering.
4189      *
4190      * <pre>
4191      * LdapComparatorDescription = LPAREN WSP
4192      *       numericoid                           ; object identifier
4193      *       [ SP "DESC" SP qdstring ]            ; description
4194      *       SP "FQCN" SP fqcn                    ; fully qualified class name
4195      *       [ SP "BYTECODE" SP base64 ]          ; optional base64 encoded bytecode
4196      *       extensions WSP RPAREN                ; extensions
4197      * 
4198      * base64          = *(4base64-char)
4199      * base64-char     = ALPHA / DIGIT / "+" / "/"
4200      * fqcn = fqcnComponent 1*( DOT fqcnComponent )
4201      * fqcnComponent = ???
4202      * </pre>
4203      * 
4204      * @param reader The stream reader
4205      * @param pos The position in the Schema
4206      * @param objectIdentifierMacros The set of existing Macros
4207      * @return An instance of LdapComparatorDescription
4208      * @throws LdapSchemaException If the schema is wrong
4209      * @throws IOException If the stream can't be read
4210      */
4211     private static LdapComparatorDescription parseLdapComparatorRelaxed( Reader reader, PosSchema pos,
4212         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 
4213             throws IOException, LdapSchemaException
4214     {
4215         // Get rid of whites, comments end empty lines
4216         skipWhites( reader, pos, false );
4217         
4218         // we must have a '('
4219         if ( pos.line.charAt( pos.start ) != LPAREN )
4220         {
4221             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
4222                 pos.lineNumber, pos.start ) );
4223         }
4224         else
4225         {
4226             pos.start++;
4227         }
4228         
4229         // Get rid of whites, comments end empty lines
4230         skipWhites( reader, pos, false );
4231         
4232         // Now, the OID. 
4233         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
4234         
4235         LdapComparatorDescription ldapComparator = new LdapComparatorDescription( oid );
4236         int elementsSeen = 0;
4237         
4238         while ( true )
4239         {
4240             if ( startsWith( reader, pos, RPAREN ) )
4241             {
4242                 pos.start++;
4243                 break;
4244             }
4245             
4246             skipWhites( reader, pos, true );
4247             
4248             if ( startsWith( pos, DESC_STR ) )
4249             {
4250                 elementsSeen = checkElement( elementsSeen, LdapComparatorElements.DESC, pos );
4251 
4252                 pos.start += DESC_STR.length();
4253                 
4254                 skipWhites( reader, pos, true );
4255 
4256                 ldapComparator.setDescription( getQDString( reader, pos ) );
4257             }
4258             else if ( startsWith( pos, FQCN_STR ) )
4259             {
4260                 elementsSeen = checkElement( elementsSeen, LdapComparatorElements.FQCN, pos );
4261 
4262                 pos.start += FQCN_STR.length();
4263                 
4264                 skipWhites( reader, pos, true );
4265 
4266                 String fqcn = getFqcn( pos );
4267                 ldapComparator.setFqcn( fqcn );
4268             }
4269             else if ( startsWith( pos, BYTECODE_STR ) )
4270             {
4271                 elementsSeen = checkElement( elementsSeen, LdapComparatorElements.BYTECODE, pos );
4272 
4273                 pos.start += BYTECODE_STR.length();
4274                 
4275                 skipWhites( reader, pos, true );
4276                 
4277                 String byteCode = getByteCode( pos );
4278                 ldapComparator.setBytecode( byteCode );
4279             }
4280             else if ( startsWith( pos, EXTENSION_PREFIX ) )
4281             {
4282                 processExtension( reader, pos, ldapComparator );
4283             }
4284             else if ( startsWith( reader, pos, RPAREN ) )
4285             {
4286                 pos.start++;
4287                 break;
4288             }
4289             else
4290             {
4291                 // This is an error
4292                 throw new LdapSchemaException( I18n.err( I18n.ERR_13825_COMP_DESCRIPTION_INVALID, 
4293                     pos.lineNumber, pos.start ) );
4294             }
4295         }
4296 
4297         return ldapComparator;
4298     }
4299 
4300     
4301     /**
4302      * Production for matching ldap syntax descriptions. It is fault-tolerant
4303      * against element ordering.
4304      *
4305      * <pre>
4306      * SyntaxDescription = LPAREN WSP
4307      *    numericoid                 ; object identifier
4308      *    [ SP "DESC" SP qdstring ]  ; description
4309      *    extensions WSP RPAREN      ; extensions
4310      * </pre>
4311      * 
4312      * @param ldapSyntaxDescription The String containing the Ldap Syntax description
4313      * @return An instance of LdapSyntax
4314      * @throws ParseException If the element was invalid
4315      */
4316     public LdapSyntax parseLdapSyntax( String ldapSyntaxDescription ) throws ParseException
4317     {
4318         if ( ( ldapSyntaxDescription == null ) || Strings.isEmpty( ldapSyntaxDescription.trim() ) )
4319         {
4320             throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
4321         }
4322         
4323         try ( Reader reader = new BufferedReader( new StringReader( ldapSyntaxDescription ) ) )
4324         {
4325             PosSchema pos = new PosSchema();
4326 
4327             if ( isQuirksModeEnabled )
4328             {
4329                 return parseLdapSyntaxRelaxed( reader, pos, objectIdentifierMacros );
4330             }
4331             else
4332             {
4333                 return parseLdapSyntaxStrict( reader, pos, objectIdentifierMacros );
4334             }
4335         }
4336         catch ( IOException | LdapSchemaException e )
4337         {
4338             throw new ParseException( e.getMessage(), 0 );
4339         }
4340     }
4341 
4342     
4343     /**
4344      * Production for matching ldap syntax descriptions. It is fault-tolerant
4345      * against element ordering.
4346      *
4347      * <pre>
4348      * SyntaxDescription = LPAREN WSP
4349      *    numericoid                 ; object identifier
4350      *    [ SP "DESC" SP qdstring ]  ; description
4351      *    extensions WSP RPAREN      ; extensions
4352      * </pre>
4353      * 
4354      * @param reader The stream reader
4355      * @param pos The position in the Schema
4356      * @param objectIdentifierMacros The set of existing Macros
4357      * @return An instance of LdapSyntax
4358      * @throws LdapSchemaException If the schema is wrong
4359      * @throws IOException If the stream can't be read
4360      */
4361     private static LdapSyntax parseLdapSyntaxStrict( Reader reader, PosSchema pos,
4362         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException
4363     {
4364         // Get rid of whites, comments end empty lines
4365         skipWhites( reader, pos, false );
4366         
4367         // we must have a '('
4368         if ( pos.line.charAt( pos.start ) != LPAREN )
4369         {
4370             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
4371                 pos.lineNumber, pos.start ) );
4372         }
4373         else
4374         {
4375             pos.start++;
4376         }
4377         
4378         // Get rid of whites, comments end empty lines
4379         skipWhites( reader, pos, false );
4380         
4381         // Now, the OID. 
4382         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
4383         
4384         // Check that the OID is valid
4385         if ( !Oid.isOid( oid ) )
4386         {
4387             throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
4388         }
4389         
4390         LdapSyntax ldapSyntax = new LdapSyntax( oid );
4391         int elementsSeen = 0;
4392         
4393         while ( true )
4394         {
4395             if ( startsWith( reader, pos, RPAREN ) )
4396             {
4397                 pos.start++;
4398                 break;
4399             }
4400             
4401             skipWhites( reader, pos, true );
4402             
4403             if ( startsWith( pos, DESC_STR ) )
4404             {
4405                 elementsSeen = checkElement( elementsSeen, LdapSyntaxElements.DESC, pos );
4406 
4407                 pos.start += DESC_STR.length();
4408                 
4409                 skipWhites( reader, pos, true );
4410 
4411                 ldapSyntax.setDescription( getQDString( reader, pos ) );
4412             }
4413             else if ( startsWith( pos, EXTENSION_PREFIX ) )
4414             {
4415                 processExtension( reader, pos, ldapSyntax );
4416             }
4417             else if ( startsWith( reader, pos, RPAREN ) )
4418             {
4419                 pos.start++;
4420                 break;
4421             }
4422             else
4423             {
4424                 // This is an error
4425                 throw new LdapSchemaException( I18n.err( I18n.ERR_13807_SYN_DESCRIPTION_INVALID, 
4426                     pos.lineNumber, pos.start ) );
4427             }
4428         }
4429         
4430         return ldapSyntax;
4431     }
4432 
4433     
4434     /**
4435      * Production for matching ldap syntax descriptions. It is fault-tolerant
4436      * against element ordering.
4437      *
4438      * <pre>
4439      * SyntaxDescription = LPAREN WSP
4440      *    numericoid                 ; object identifier
4441      *    [ SP "DESC" SP qdstring ]  ; description
4442      *    extensions WSP RPAREN      ; extensions
4443      * </pre>
4444      * 
4445      * @param reader The stream reader
4446      * @param pos The position in the Schema
4447      * @param objectIdentifierMacros The set of existing Macros
4448      * @return An instance of LdapSyntax
4449      * @throws LdapSchemaException If the schema is wrong
4450      * @throws IOException If the stream can't be read
4451      */
4452     private static LdapSyntax parseLdapSyntaxRelaxed( Reader reader, PosSchema pos,
4453         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException
4454     {
4455         // Get rid of whites, comments end empty lines
4456         skipWhites( reader, pos, false );
4457         
4458         // we must have a '('
4459         if ( pos.line.charAt( pos.start ) != LPAREN )
4460         {
4461             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
4462                 pos.lineNumber, pos.start ) );
4463         }
4464         else
4465         {
4466             pos.start++;
4467         }
4468         
4469         // Get rid of whites, comments end empty lines
4470         skipWhites( reader, pos, false );
4471         
4472         // Now, the OID. 
4473         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
4474         
4475         LdapSyntax ldapSyntax = new LdapSyntax( oid );
4476         int elementsSeen = 0;
4477         
4478         while ( true )
4479         {
4480             if ( startsWith( reader, pos, RPAREN ) )
4481             {
4482                 pos.start++;
4483                 break;
4484             }
4485             
4486             skipWhites( reader, pos, true );
4487             
4488             if ( startsWith( pos, DESC_STR ) )
4489             {
4490                 elementsSeen = checkElement( elementsSeen, LdapSyntaxElements.DESC, pos );
4491 
4492                 pos.start += DESC_STR.length();
4493                 
4494                 skipWhites( reader, pos, true );
4495 
4496                 ldapSyntax.setDescription( getQDString( reader, pos ) );
4497             }
4498             else if ( startsWith( pos, EXTENSION_PREFIX ) )
4499             {
4500                 processExtension( reader, pos, ldapSyntax );
4501             }
4502             else if ( startsWith( reader, pos, RPAREN ) )
4503             {
4504                 pos.start++;
4505                 break;
4506             }
4507             else
4508             {
4509                 // This is an error
4510                 throw new LdapSchemaException( I18n.err( I18n.ERR_13807_SYN_DESCRIPTION_INVALID, 
4511                     pos.lineNumber, pos.start ) );
4512             }
4513         }
4514         
4515         return ldapSyntax;
4516     }
4517     
4518     
4519     /**
4520      * Production for matching MatchingRule descriptions. It is fault-tolerant
4521      * against element ordering.
4522      *
4523      * <pre>
4524      * MatchingRuleDescription = LPAREN WSP
4525      *    numericoid                 ; object identifier
4526      *    [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
4527      *    [ SP "DESC" SP qdstring ]  ; description
4528      *    [ SP "OBSOLETE" ]          ; not active
4529      *    SP "SYNTAX" SP numericoid  ; assertion syntax
4530      *    extensions WSP RPAREN      ; extensions
4531      * </pre>
4532      * 
4533      * @param matchingRuleDescription The String containing the MatchingRuledescription
4534      * @return An instance of MatchingRule
4535      * @throws ParseException If the element was invalid
4536      */
4537     public MatchingRule parseMatchingRule( String matchingRuleDescription ) throws ParseException
4538     {
4539         if ( ( matchingRuleDescription == null ) || Strings.isEmpty( matchingRuleDescription.trim() ) )
4540         {
4541             throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
4542         }
4543         
4544         try ( Reader reader = new BufferedReader( new StringReader( matchingRuleDescription ) ) )
4545         {
4546             PosSchema pos = new PosSchema();
4547 
4548             if ( isQuirksModeEnabled )
4549             {
4550                 return parseMatchingRuleRelaxed( reader, pos, objectIdentifierMacros );
4551             }
4552             else
4553             {
4554                 return parseMatchingRuleStrict( reader, pos, objectIdentifierMacros );
4555             }
4556         }
4557         catch ( IOException | LdapSchemaException e )
4558         {
4559             throw new ParseException( e.getMessage(), 0 );
4560         }
4561     }
4562 
4563     
4564     /**
4565      * Production for matching rule descriptions. It is fault-tolerant
4566      * against element ordering.
4567      *
4568      * <pre>
4569      * MatchingRuleDescription = LPAREN WSP
4570      *    numericoid                 ; object identifier
4571      *    [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
4572      *    [ SP "DESC" SP qdstring ]  ; description
4573      *    [ SP "OBSOLETE" ]          ; not active
4574      *    SP "SYNTAX" SP numericoid  ; assertion syntax
4575      *    extensions WSP RPAREN      ; extensions
4576      * </pre>
4577      * 
4578      * @param reader The stream reader
4579      * @param pos The position in the Schema
4580      * @param objectIdentifierMacros The set of existing Macros
4581      * @return An instance of MatchingRule
4582      * @throws LdapSchemaException If the schema is wrong
4583      * @throws IOException If the stream can't be read
4584      */
4585     private static MatchingRule parseMatchingRuleStrict( Reader reader, PosSchema pos, 
4586         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException
4587     {
4588         // Get rid of whites, comments end empty lines
4589         skipWhites( reader, pos, false );
4590         
4591         // we must have a '('
4592         if ( pos.line.charAt( pos.start ) != LPAREN )
4593         {
4594             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
4595                 pos.lineNumber, pos.start ) );
4596         }
4597         else
4598         {
4599             pos.start++;
4600         }
4601         
4602         // Get rid of whites, comments end empty lines
4603         skipWhites( reader, pos, false );
4604         
4605         // Now, the OID. 
4606         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
4607         
4608         // Check that the OID is valid
4609         if ( !Oid.isOid( oid ) )
4610         {
4611             throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
4612         }
4613         
4614         MatchingRule matchingRule = new MatchingRule( oid );
4615         int elementsSeen = 0;
4616         boolean hasSyntax = false;
4617         
4618         while ( true )
4619         {
4620             if ( startsWith( reader, pos, RPAREN ) )
4621             {
4622                 pos.start++;
4623                 break;
4624             }
4625             
4626             skipWhites( reader, pos, true );
4627             
4628             if ( startsWith( pos, NAME_STR ) )
4629             {
4630                 elementsSeen = checkElement( elementsSeen, MatchingRuleElements.NAME, pos );
4631 
4632                 pos.start += NAME_STR.length();
4633                 
4634                 skipWhites( reader, pos, true );
4635 
4636                 matchingRule.setNames( getQDescrs( reader, pos, STRICT ) );
4637             }
4638             else if ( startsWith( pos, DESC_STR ) )
4639             {
4640                 elementsSeen = checkElement( elementsSeen, MatchingRuleElements.DESC, pos );
4641 
4642                 pos.start += DESC_STR.length();
4643                 
4644                 skipWhites( reader, pos, true );
4645 
4646                 matchingRule.setDescription( getQDString( reader, pos ) );
4647             }
4648             else if ( startsWith( pos, OBSOLETE_STR ) )
4649             {
4650                 elementsSeen = checkElement( elementsSeen, MatchingRuleElements.OBSOLETE, pos );
4651 
4652                 pos.start += OBSOLETE_STR.length();
4653                 
4654                 matchingRule.setObsolete( true );
4655             }
4656             else if ( startsWith( pos, SYNTAX_STR ) )
4657             {
4658                 elementsSeen = checkElement( elementsSeen, MatchingRuleElements.SYNTAX, pos );
4659 
4660                 pos.start += SYNTAX_STR.length();
4661                 
4662                 skipWhites( reader, pos, true );
4663                 
4664                 String syntaxOid = getNumericOid( pos );
4665 
4666                 matchingRule.setSyntaxOid( syntaxOid );
4667                 hasSyntax = true;
4668             }
4669             else if ( startsWith( pos, EXTENSION_PREFIX ) )
4670             {
4671                 processExtension( reader, pos, matchingRule );
4672             }
4673             else if ( startsWith( reader, pos, RPAREN ) )
4674             {
4675                 pos.start++;
4676                 break;
4677             }
4678             else
4679             {
4680                 // This is an error
4681                 throw new LdapSchemaException( I18n.err( I18n.ERR_13781_MR_DESCRIPTION_INVALID, 
4682                     pos.lineNumber, pos.start ) );
4683             }
4684         }
4685         
4686         // Semantic checks
4687         if ( !hasSyntax )
4688         {
4689             throw new LdapSchemaException( I18n.err( I18n.ERR_13808_SYNTAX_REQUIRED, 
4690                 pos.lineNumber, pos.start ) );
4691         }
4692 
4693         return matchingRule;
4694     }
4695 
4696     
4697     /**
4698      * Production for matching rule descriptions. It is fault-tolerant
4699      * against element ordering.
4700      *
4701      * <pre>
4702      * MatchingRuleDescription = LPAREN WSP
4703      *    numericoid                 ; object identifier
4704      *    [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
4705      *    [ SP "DESC" SP qdstring ]  ; description
4706      *    [ SP "OBSOLETE" ]          ; not active
4707      *    SP "SYNTAX" SP numericoid  ; assertion syntax
4708      *    extensions WSP RPAREN      ; extensions
4709      * </pre>
4710      * 
4711      * @param reader The stream reader
4712      * @param pos The position in the Schema
4713      * @param objectIdentifierMacros The set of existing Macros
4714      * @return An instance of MatchingRule
4715      * @throws LdapSchemaException If the schema is wrong
4716      * @throws IOException If the stream can't be read
4717      */
4718     private static MatchingRule parseMatchingRuleRelaxed( Reader reader, PosSchema pos,
4719         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 
4720             throws IOException, LdapSchemaException
4721     {
4722         // Get rid of whites, comments end empty lines
4723         skipWhites( reader, pos, false );
4724         
4725         // we must have a '('
4726         if ( pos.line.charAt( pos.start ) != LPAREN )
4727         {
4728             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
4729                 pos.lineNumber, pos.start ) );
4730         }
4731         else
4732         {
4733             pos.start++;
4734         }
4735         
4736         // Get rid of whites, comments end empty lines
4737         skipWhites( reader, pos, false );
4738         
4739         // Now, the OID. 
4740         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
4741         
4742         MatchingRule matchingRule = new MatchingRule( oid );
4743         int elementsSeen = 0;
4744         
4745         while ( true )
4746         {
4747             if ( startsWith( reader, pos, RPAREN ) )
4748             {
4749                 pos.start++;
4750                 break;
4751             }
4752             
4753             skipWhites( reader, pos, true );
4754             
4755             if ( startsWith( pos, NAME_STR ) )
4756             {
4757                 elementsSeen = checkElement( elementsSeen, MatchingRuleElements.NAME, pos );
4758 
4759                 pos.start += NAME_STR.length();
4760                 
4761                 skipWhites( reader, pos, true );
4762 
4763                 matchingRule.setNames( getQDescrs( reader, pos, RELAXED ) );
4764             }
4765             else if ( startsWith( pos, DESC_STR ) )
4766             {
4767                 elementsSeen = checkElement( elementsSeen, MatchingRuleElements.DESC, pos );
4768 
4769                 pos.start += DESC_STR.length();
4770                 
4771                 skipWhites( reader, pos, true );
4772 
4773                 matchingRule.setDescription( getQDString( reader, pos ) );
4774             }
4775             else if ( startsWith( pos, OBSOLETE_STR ) )
4776             {
4777                 elementsSeen = checkElement( elementsSeen, MatchingRuleElements.OBSOLETE, pos );
4778 
4779                 pos.start += OBSOLETE_STR.length();
4780                 
4781                 matchingRule.setObsolete( true );
4782             }
4783             else if ( startsWith( pos, SYNTAX_STR ) )
4784             {
4785                 elementsSeen = checkElement( elementsSeen, MatchingRuleElements.SYNTAX, pos );
4786 
4787                 pos.start += SYNTAX_STR.length();
4788                 
4789                 skipWhites( reader, pos, true );
4790                 
4791                 String syntaxOid = getNumericOid( pos );
4792 
4793                 matchingRule.setSyntaxOid( syntaxOid );
4794             }
4795             else if ( startsWith( pos, EXTENSION_PREFIX ) )
4796             {
4797                 processExtension( reader, pos, matchingRule );
4798             }
4799             else if ( startsWith( reader, pos, RPAREN ) )
4800             {
4801                 pos.start++;
4802                 break;
4803             }
4804             else
4805             {
4806                 // This is an error
4807                 throw new LdapSchemaException( I18n.err( I18n.ERR_13781_MR_DESCRIPTION_INVALID, 
4808                     pos.lineNumber, pos.start ) );
4809             }
4810         }
4811 
4812         return matchingRule;
4813     }
4814 
4815     
4816     /**
4817      * Production for matching MatchingRuleUse descriptions. It is fault-tolerant
4818      * against element ordering.
4819      *
4820      * <pre>
4821      * MatchingRuleUseDescription = LPAREN WSP
4822      *    numericoid                 ; object identifier
4823      *    [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
4824      *    [ SP "DESC" SP qdstring ]  ; description
4825      *    [ SP "OBSOLETE" ]          ; not active
4826      *    SP "APPLIES" SP oids       ; attribute types
4827      *    extensions WSP RPAREN      ; extensions
4828      * </pre>
4829      * 
4830      * @param matchingRuleUseDescription The String containing the MatchingRuleUsedescription
4831      * @return An instance of MatchingRuleUse
4832      * @throws ParseException If the element was invalid
4833      */
4834     public MatchingRuleUse parseMatchingRuleUse( String matchingRuleUseDescription ) throws ParseException
4835     {
4836         if ( ( matchingRuleUseDescription == null ) || Strings.isEmpty( matchingRuleUseDescription.trim() ) )
4837         {
4838             throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
4839         }
4840         
4841         try ( Reader reader = new BufferedReader( new StringReader( matchingRuleUseDescription ) ) )
4842         {
4843             PosSchema pos = new PosSchema();
4844 
4845             if ( isQuirksModeEnabled )
4846             {
4847                 return parseMatchingRuleUseRelaxed( reader, pos, objectIdentifierMacros );
4848             }
4849             else
4850             {
4851                 return parseMatchingRuleUseStrict( reader, pos, objectIdentifierMacros );
4852             }
4853         }
4854         catch ( IOException | LdapSchemaException e )
4855         {
4856             throw new ParseException( e.getMessage(), 0 );
4857         }
4858     }
4859 
4860     
4861     /**
4862      * Production for MatchingRuleUse descriptions. It is fault-tolerant
4863      * against element ordering.
4864      *
4865      * <pre>
4866      * MatchingRuleUseDescription = LPAREN WSP
4867      *    numericoid                 ; object identifier
4868      *    [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
4869      *    [ SP "DESC" SP qdstring ]  ; description
4870      *    [ SP "OBSOLETE" ]          ; not active
4871      *    SP "APPLIES" SP oids       ; attribute types
4872      *    extensions WSP RPAREN      ; extensions
4873      * </pre>
4874      * 
4875      * @param reader The stream reader
4876      * @param pos The position in the Schema
4877      * @param objectIdentifierMacros The set of existing Macros
4878      * @return An instance of MatchingRuleUse
4879      * @throws LdapSchemaException If the schema is wrong
4880      * @throws IOException If the stream can't be read
4881      */
4882     private static MatchingRuleUse parseMatchingRuleUseStrict( Reader reader, PosSchema pos,
4883         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException
4884     {
4885         // Get rid of whites, comments end empty lines
4886         skipWhites( reader, pos, false );
4887         
4888         // we must have a '('
4889         if ( pos.line.charAt( pos.start ) != LPAREN )
4890         {
4891             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
4892                 pos.lineNumber, pos.start ) );
4893         }
4894         else
4895         {
4896             pos.start++;
4897         }
4898         
4899         // Get rid of whites, comments end empty lines
4900         skipWhites( reader, pos, false );
4901         
4902         // Now, the OID. 
4903         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
4904         
4905         // Check that the OID is valid
4906         if ( !Oid.isOid( oid ) )
4907         {
4908             throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
4909         }
4910         
4911         MatchingRuleUse matchingRuleUse = new MatchingRuleUse( oid );
4912         int elementsSeen = 0;
4913         boolean hasApplies = false;
4914         
4915         while ( true )
4916         {
4917             if ( startsWith( reader, pos, RPAREN ) )
4918             {
4919                 pos.start++;
4920                 break;
4921             }
4922             
4923             skipWhites( reader, pos, false );
4924             
4925             if ( startsWith( pos, NAME_STR ) )
4926             {
4927                 elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.NAME, pos );
4928 
4929                 pos.start += NAME_STR.length();
4930                 
4931                 skipWhites( reader, pos, true );
4932 
4933                 matchingRuleUse.setNames( getQDescrs( reader, pos, STRICT ) );
4934             }
4935             else if ( startsWith( pos, DESC_STR ) )
4936             {
4937                 elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.DESC, pos );
4938 
4939                 pos.start += DESC_STR.length();
4940                 
4941                 skipWhites( reader, pos, true );
4942 
4943                 matchingRuleUse.setDescription( getQDString( reader, pos ) );
4944             }
4945             else if ( startsWith( pos, OBSOLETE_STR ) )
4946             {
4947                 elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.OBSOLETE, pos );
4948 
4949                 pos.start += OBSOLETE_STR.length();
4950                 
4951                 matchingRuleUse.setObsolete( true );
4952             }
4953             else if ( startsWith( pos, APPLIES_STR ) )
4954             {
4955                 elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.APPLIES, pos );
4956 
4957                 pos.start += APPLIES_STR.length();
4958                 
4959                 skipWhites( reader, pos, true );
4960                 
4961                 List<String> oids = getOidsStrict( reader, pos );
4962 
4963                 matchingRuleUse.setApplicableAttributeOids( oids );
4964                 hasApplies = true;
4965             }
4966             else if ( startsWith( pos, EXTENSION_PREFIX ) )
4967             {
4968                 processExtension( reader, pos, matchingRuleUse );
4969             }
4970             else if ( startsWith( reader, pos, RPAREN ) )
4971             {
4972                 pos.start++;
4973                 break;
4974             }
4975             else
4976             {
4977                 // This is an error
4978                 throw new LdapSchemaException( I18n.err( I18n.ERR_13815_MRU_DESCRIPTION_INVALID, 
4979                     pos.lineNumber, pos.start ) );
4980             }
4981         }
4982         
4983         // Semantic checks
4984         if ( !hasApplies )
4985         {
4986             throw new LdapSchemaException( I18n.err( I18n.ERR_13814_APPLIES_REQUIRED, 
4987                 pos.lineNumber, pos.start ) );
4988         }
4989 
4990         return matchingRuleUse;
4991     }
4992 
4993     
4994     /**
4995      * Production for MatchingRuleUse descriptions. It is fault-tolerant
4996      * against element ordering.
4997      *
4998      * <pre>
4999      * MatchingRuleUseDescription = LPAREN WSP
5000      *    numericoid                 ; object identifier
5001      *    [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
5002      *    [ SP "DESC" SP qdstring ]  ; description
5003      *    [ SP "OBSOLETE" ]          ; not active
5004      *    SP "APPLIES" SP oids       ; attribute types
5005      *    extensions WSP RPAREN      ; extensions
5006      * </pre>
5007      * 
5008      * @param reader The stream reader
5009      * @param pos The position in the Schema
5010      * @param objectIdentifierMacros The set of existing Macros
5011      * @return An instance of MatchingRuleUse
5012      * @throws LdapSchemaException If the schema is wrong
5013      * @throws IOException If the stream can't be read
5014      */
5015     private static MatchingRuleUse parseMatchingRuleUseRelaxed( Reader reader, PosSchema pos,
5016         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 
5017             throws IOException, LdapSchemaException
5018     {
5019         // Get rid of whites, comments end empty lines
5020         skipWhites( reader, pos, false );
5021         
5022         // we must have a '('
5023         if ( pos.line.charAt( pos.start ) != LPAREN )
5024         {
5025             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
5026                 pos.lineNumber, pos.start ) );
5027         }
5028         else
5029         {
5030             pos.start++;
5031         }
5032         
5033         // Get rid of whites, comments end empty lines
5034         skipWhites( reader, pos, false );
5035         
5036         // Now, the OID. 
5037         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
5038         
5039         MatchingRuleUse matchingRuleUse = new MatchingRuleUse( oid );
5040         int elementsSeen = 0;
5041         
5042         while ( true )
5043         {
5044             if ( startsWith( reader, pos, RPAREN ) )
5045             {
5046                 pos.start++;
5047                 break;
5048             }
5049             
5050             skipWhites( reader, pos, true );
5051             
5052             if ( startsWith( pos, NAME_STR ) )
5053             {
5054                 elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.NAME, pos );
5055 
5056                 pos.start += NAME_STR.length();
5057                 
5058                 skipWhites( reader, pos, true );
5059 
5060                 matchingRuleUse.setNames( getQDescrs( reader, pos, RELAXED ) );
5061             }
5062             else if ( startsWith( pos, DESC_STR ) )
5063             {
5064                 elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.DESC, pos );
5065 
5066                 pos.start += DESC_STR.length();
5067                 
5068                 skipWhites( reader, pos, true );
5069 
5070                 matchingRuleUse.setDescription( getQDString( reader, pos ) );
5071             }
5072             else if ( startsWith( pos, OBSOLETE_STR ) )
5073             {
5074                 elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.OBSOLETE, pos );
5075 
5076                 pos.start += OBSOLETE_STR.length();
5077                 
5078                 matchingRuleUse.setObsolete( true );
5079             }
5080             else if ( startsWith( pos, APPLIES_STR ) )
5081             {
5082                 elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.APPLIES, pos );
5083 
5084                 pos.start += APPLIES_STR.length();
5085                 
5086                 skipWhites( reader, pos, true );
5087                 
5088                 List<String> oids = getOidsRelaxed( reader, pos );
5089 
5090                 matchingRuleUse.setApplicableAttributeOids( oids );
5091             }
5092             else if ( startsWith( pos, EXTENSION_PREFIX ) )
5093             {
5094                 processExtension( reader, pos, matchingRuleUse );
5095             }
5096             else if ( startsWith( reader, pos, RPAREN ) )
5097             {
5098                 pos.start++;
5099                 break;
5100             }
5101             else
5102             {
5103                 // This is an error
5104                 throw new LdapSchemaException( I18n.err( I18n.ERR_13815_MRU_DESCRIPTION_INVALID, 
5105                     pos.lineNumber, pos.start ) );
5106             }
5107         }
5108 
5109         return matchingRuleUse;
5110     }
5111 
5112     
5113     /**
5114      * Production for NameForm descriptions. It is fault-tolerant
5115      * against element ordering.
5116      *
5117      * <pre>
5118      * NameFormDescription = LPAREN WSP
5119      *    numericoid                 ; object identifier
5120      *    [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
5121      *    [ SP "DESC" SP qdstring ]  ; description
5122      *    [ SP "OBSOLETE" ]          ; not active
5123      *    SP "OC" SP oid             ; structural object class
5124      *    SP "MUST" SP oids          ; attribute types
5125      *    [ SP "MAY" SP oids ]       ; attribute types
5126      *    extensions WSP RPAREN      ; extensions
5127      * </pre>
5128      * 
5129      * @param nameFormDescription The String containing the NameFormdescription
5130      * @return An instance of NameForm
5131      * @throws ParseException If the element was invalid
5132      */
5133     public NameForm parseNameForm( String nameFormDescription ) throws ParseException
5134     {
5135         if ( ( nameFormDescription == null ) || Strings.isEmpty( nameFormDescription.trim() ) )
5136         {
5137             throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
5138         }
5139         
5140         try ( Reader reader = new BufferedReader( new StringReader( nameFormDescription ) ) )
5141         {
5142             PosSchema pos = new PosSchema();
5143 
5144             if ( isQuirksModeEnabled )
5145             {
5146                 return parseNameFormRelaxed( reader, pos, objectIdentifierMacros );
5147             }
5148             else
5149             {
5150                 return parseNameFormStrict( reader, pos, objectIdentifierMacros );
5151             }
5152         }
5153         catch ( IOException | LdapSchemaException e )
5154         {
5155             throw new ParseException( e.getMessage(), 0 );
5156         }
5157     }
5158 
5159     
5160     /**
5161      * Production for NameForm descriptions. It is fault-tolerant
5162      * against element ordering.
5163      *
5164      * <pre>
5165      * NameFormDescription = LPAREN WSP
5166      *    numericoid                 ; object identifier
5167      *    [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
5168      *    [ SP "DESC" SP qdstring ]  ; description
5169      *    [ SP "OBSOLETE" ]          ; not active
5170      *    SP "OC" SP oid             ; structural object class
5171      *    SP "MUST" SP oids          ; attribute types
5172      *    [ SP "MAY" SP oids ]       ; attribute types
5173      *    extensions WSP RPAREN      ; extensions
5174      * </pre>
5175      * 
5176      * @param reader The stream reader
5177      * @param pos The position in the Schema
5178      * @return An instance of NameForm
5179      * @param objectIdentifierMacros The set of existing Macros
5180      * @throws LdapSchemaException If the schema is wrong
5181      * @throws IOException If the stream can't be read
5182      */
5183     private static NameForm parseNameFormStrict( Reader reader, PosSchema pos,
5184         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException
5185     {
5186         // Get rid of whites, comments end empty lines
5187         skipWhites( reader, pos, false );
5188         
5189         // we must have a '('
5190         if ( pos.line.charAt( pos.start ) != LPAREN )
5191         {
5192             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
5193                 pos.lineNumber, pos.start ) );
5194         }
5195         else
5196         {
5197             pos.start++;
5198         }
5199         
5200         // Get rid of whites, comments end empty lines
5201         skipWhites( reader, pos, false );
5202         
5203         // Now, the OID. 
5204         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
5205         
5206         // Check that the OID is valid
5207         if ( !Oid.isOid( oid ) )
5208         {
5209             throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
5210         }
5211         
5212         NameForm nameForm = new NameForm( oid );
5213         int elementsSeen = 0;
5214         boolean hasOc = false;
5215         boolean hasMust = false;
5216         
5217         while ( true )
5218         {
5219             if ( startsWith( reader, pos, RPAREN ) )
5220             {
5221                 pos.start++;
5222                 break;
5223             }
5224             
5225             skipWhites( reader, pos, true );
5226 
5227             if ( startsWith( pos, NAME_STR ) )
5228             {
5229                 elementsSeen = checkElement( elementsSeen, NameFormElements.NAME, pos );
5230 
5231                 pos.start += NAME_STR.length();
5232                 
5233                 skipWhites( reader, pos, true );
5234 
5235                 nameForm.setNames( getQDescrs( reader, pos, STRICT ) );
5236             }
5237             else if ( startsWith( pos, DESC_STR ) )
5238             {
5239                 elementsSeen = checkElement( elementsSeen, NameFormElements.DESC, pos );
5240 
5241                 pos.start += DESC_STR.length();
5242                 
5243                 skipWhites( reader, pos, true );
5244 
5245                 nameForm.setDescription( getQDString( reader, pos ) );
5246             }
5247             else if ( startsWith( pos, OBSOLETE_STR ) )
5248             {
5249                 elementsSeen = checkElement( elementsSeen, NameFormElements.OBSOLETE, pos );
5250 
5251                 pos.start += OBSOLETE_STR.length();
5252                 
5253                 nameForm.setObsolete( true );
5254             }
5255             else if ( startsWith( pos, OC_STR ) )
5256             {
5257                 elementsSeen = checkElement( elementsSeen, NameFormElements.OC, pos );
5258 
5259                 pos.start += OC_STR.length();
5260                 
5261                 skipWhites( reader, pos, true );
5262                 
5263                 String oc = getOidStrict( pos );
5264 
5265                 nameForm.setStructuralObjectClassOid( oc );
5266                 hasOc = true;
5267             }
5268             else if ( startsWith( pos, MUST_STR ) )
5269             {
5270                 elementsSeen = checkElement( elementsSeen, NameFormElements.MUST, pos );
5271 
5272                 pos.start += MUST_STR.length();
5273                 
5274                 skipWhites( reader, pos, true );
5275                 
5276                 List<String> must = getOidsStrict( reader, pos );
5277 
5278                 nameForm.setMustAttributeTypeOids( must );
5279                 hasMust = true;
5280             }
5281             else if ( startsWith( pos, MAY_STR ) )
5282             {
5283                 elementsSeen = checkElement( elementsSeen, NameFormElements.MAY, pos );
5284 
5285                 pos.start += MAY_STR.length();
5286                 
5287                 skipWhites( reader, pos, true );
5288                 
5289                 List<String> may = getOidsStrict( reader, pos );
5290 
5291                 nameForm.setMayAttributeTypeOids( may );
5292             }
5293             else if ( startsWith( pos, EXTENSION_PREFIX ) )
5294             {
5295                 processExtension( reader, pos, nameForm );
5296             }
5297             else if ( startsWith( reader, pos, RPAREN ) )
5298             {
5299                 pos.start++;
5300                 break;
5301             }
5302             else
5303             {
5304                 // This is an error
5305                 throw new LdapSchemaException( I18n.err( I18n.ERR_13816_NF_DESCRIPTION_INVALID, 
5306                     pos.lineNumber, pos.start ) );
5307             }
5308         }
5309         
5310         // Semantic checks
5311         if ( !hasOc )
5312         {
5313             throw new LdapSchemaException( I18n.err( I18n.ERR_13817_STRUCTURAL_OBJECT_CLASS_REQUIRED, 
5314                 pos.lineNumber, pos.start ) );
5315         }
5316 
5317         if ( !hasMust )
5318         {
5319             throw new LdapSchemaException( I18n.err( I18n.ERR_13818_MUST_REQUIRED, 
5320                 pos.lineNumber, pos.start ) );
5321         }
5322 
5323         return nameForm;
5324     }
5325 
5326     
5327     /**
5328      * Production for NameForm descriptions. It is fault-tolerant
5329      * against element ordering.
5330      *
5331      * <pre>
5332      * NameFormDescription = LPAREN WSP
5333      *    numericoid                 ; object identifier
5334      *    [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
5335      *    [ SP "DESC" SP qdstring ]  ; description
5336      *    [ SP "OBSOLETE" ]          ; not active
5337      *    SP "OC" SP oid             ; structural object class
5338      *    SP "MUST" SP oids          ; attribute types
5339      *    [ SP "MAY" SP oids ]       ; attribute types
5340      *    extensions WSP RPAREN      ; extensions
5341      * </pre>
5342      * 
5343      * @param reader The stream reader
5344      * @param pos The position in the Schema
5345      * @param objectIdentifierMacros The set of existing Macros
5346      * @return An instance of NameForm
5347      * @throws LdapSchemaException If the schema is wrong
5348      * @throws IOException If the stream can't be read
5349      */
5350     private static NameForm parseNameFormRelaxed( Reader reader, PosSchema pos,
5351         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 
5352             throws IOException, LdapSchemaException
5353     {
5354         // Get rid of whites, comments end empty lines
5355         skipWhites( reader, pos, false );
5356         
5357         // we must have a '('
5358         if ( pos.line.charAt( pos.start ) != LPAREN )
5359         {
5360             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
5361                 pos.lineNumber, pos.start ) );
5362         }
5363         else
5364         {
5365             pos.start++;
5366         }
5367         
5368         // Get rid of whites, comments end empty lines
5369         skipWhites( reader, pos, false );
5370         
5371         // Now, the OID. 
5372         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
5373         
5374         NameForm nameForm = new NameForm( oid );
5375         int elementsSeen = 0;
5376         
5377         while ( true )
5378         {
5379             if ( startsWith( reader, pos, RPAREN ) )
5380             {
5381                 pos.start++;
5382                 break;
5383             }
5384             
5385             skipWhites( reader, pos, true );
5386 
5387             if ( startsWith( pos, NAME_STR ) )
5388             {
5389                 elementsSeen = checkElement( elementsSeen, NameFormElements.NAME, pos );
5390 
5391                 pos.start += NAME_STR.length();
5392                 
5393                 skipWhites( reader, pos, true );
5394 
5395                 nameForm.setNames( getQDescrs( reader, pos, RELAXED ) );
5396             }
5397             else if ( startsWith( pos, DESC_STR ) )
5398             {
5399                 elementsSeen = checkElement( elementsSeen, NameFormElements.DESC, pos );
5400 
5401                 pos.start += DESC_STR.length();
5402                 
5403                 skipWhites( reader, pos, true );
5404 
5405                 nameForm.setDescription( getQDString( reader, pos ) );
5406             }
5407             else if ( startsWith( pos, OBSOLETE_STR ) )
5408             {
5409                 elementsSeen = checkElement( elementsSeen, NameFormElements.OBSOLETE, pos );
5410 
5411                 pos.start += OBSOLETE_STR.length();
5412                 
5413                 nameForm.setObsolete( true );
5414             }
5415             else if ( startsWith( pos, OC_STR ) )
5416             {
5417                 elementsSeen = checkElement( elementsSeen, NameFormElements.OC, pos );
5418 
5419                 pos.start += OC_STR.length();
5420                 
5421                 skipWhites( reader, pos, true );
5422                 
5423                 String oc = getOidRelaxed( pos, UN_QUOTED );
5424 
5425                 nameForm.setStructuralObjectClassOid( oc );
5426             }
5427             else if ( startsWith( pos, MUST_STR ) )
5428             {
5429                 elementsSeen = checkElement( elementsSeen, NameFormElements.MUST, pos );
5430 
5431                 pos.start += MUST_STR.length();
5432                 
5433                 skipWhites( reader, pos, true );
5434                 
5435                 List<String> must = getOidsRelaxed( reader, pos );
5436 
5437                 nameForm.setMustAttributeTypeOids( must );
5438             }
5439             else if ( startsWith( pos, MAY_STR ) )
5440             {
5441                 elementsSeen = checkElement( elementsSeen, NameFormElements.MAY, pos );
5442 
5443                 pos.start += MAY_STR.length();
5444                 
5445                 skipWhites( reader, pos, true );
5446                 
5447                 List<String> may = getOidsRelaxed( reader, pos );
5448 
5449                 nameForm.setMayAttributeTypeOids( may );
5450             }
5451             else if ( startsWith( pos, EXTENSION_PREFIX ) )
5452             {
5453                 processExtension( reader, pos, nameForm );
5454             }
5455             else if ( startsWith( reader, pos, RPAREN ) )
5456             {
5457                 pos.start++;
5458                 break;
5459             }
5460             else
5461             {
5462                 // This is an error
5463                 throw new LdapSchemaException( I18n.err( I18n.ERR_13816_NF_DESCRIPTION_INVALID, 
5464                     pos.lineNumber, pos.start ) );
5465             }
5466         }
5467         
5468         return nameForm;
5469     }
5470 
5471     
5472     /**
5473      * Production for Normalizer descriptions. It is fault-tolerant
5474      * against element ordering.
5475      *
5476      * <pre>
5477      * NormalizerDescription = LPAREN WSP
5478      *       numericoid                           ; object identifier
5479      *       [ SP "DESC" SP qdstring ]            ; description
5480      *       SP "FQCN" SP fqcn                    ; fully qualified class name
5481      *       [ SP "BYTECODE" SP base64 ]          ; optional base64 encoded bytecode
5482      *       extensions WSP RPAREN                ; extensions
5483      * 
5484      * base64          = *(4base64-char)
5485      * base64-char     = ALPHA / DIGIT / "+" / "/"
5486      * fqcn = fqcnComponent 1*( DOT fqcnComponent )
5487      * fqcnComponent = ???
5488      * </pre>
5489      * 
5490      * @param normalizerDescription The String containing the NormalizerDescription
5491      * @return An instance of NormalizerDescription
5492      * @throws ParseException If the element was invalid
5493      */
5494     public NormalizerDescription parseNormalizer( String normalizerDescription ) throws ParseException
5495     {
5496         if ( ( normalizerDescription == null ) || Strings.isEmpty( normalizerDescription.trim() ) )
5497         {
5498             throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
5499         }
5500         
5501         try ( Reader reader = new BufferedReader( new StringReader( normalizerDescription ) ) )
5502         {
5503             PosSchema pos = new PosSchema();
5504 
5505             if ( isQuirksModeEnabled )
5506             {
5507                 return parseNormalizerRelaxed( reader, pos, objectIdentifierMacros );
5508             }
5509             else
5510             {
5511                 return parseNormalizerStrict( reader, pos, objectIdentifierMacros );
5512             }
5513         }
5514         catch ( IOException | LdapSchemaException e )
5515         {
5516             throw new ParseException( e.getMessage(), 0 );
5517         }
5518     }
5519 
5520     
5521     /**
5522      * Production for Normalizer descriptions. It is fault-tolerant
5523      * against element ordering.
5524      *
5525      * <pre>
5526      * NormalizerDescription = LPAREN WSP
5527      *       numericoid                           ; object identifier
5528      *       [ SP "DESC" SP qdstring ]            ; description
5529      *       SP "FQCN" SP fqcn                    ; fully qualified class name
5530      *       [ SP "BYTECODE" SP base64 ]          ; optional base64 encoded bytecode
5531      *       extensions WSP RPAREN                ; extensions
5532      * 
5533      * base64          = *(4base64-char)
5534      * base64-char     = ALPHA / DIGIT / "+" / "/"
5535      * fqcn = fqcnComponent 1*( DOT fqcnComponent )
5536      * fqcnComponent = ???
5537      * </pre>
5538      * 
5539      * @param reader The stream reader
5540      * @param pos The position in the Schema
5541      * @param objectIdentifierMacros The set of existing Macros
5542      * @return An instance of NormalizerDescription
5543      * @throws LdapSchemaException If the schema is wrong
5544      * @throws IOException If the stream can't be read
5545      */
5546     private static NormalizerDescription parseNormalizerStrict( Reader reader, PosSchema pos,
5547         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException
5548     {
5549         // Get rid of whites, comments end empty lines
5550         skipWhites( reader, pos, false );
5551         
5552         // we must have a '('
5553         if ( pos.line.charAt( pos.start ) != LPAREN )
5554         {
5555             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
5556                 pos.lineNumber, pos.start ) );
5557         }
5558         else
5559         {
5560             pos.start++;
5561         }
5562         
5563         // Get rid of whites, comments end empty lines
5564         skipWhites( reader, pos, false );
5565         
5566         // Now, the OID. 
5567         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
5568         
5569         // Check that the OID is valid
5570         if ( !Oid.isOid( oid ) )
5571         {
5572             throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
5573         }
5574         
5575         NormalizerDescription normalizer = new NormalizerDescription( oid );
5576         int elementsSeen = 0;
5577         boolean hasFqcn = false;
5578         boolean hasByteCode = false;
5579         
5580         while ( true )
5581         {
5582             if ( startsWith( reader, pos, RPAREN ) )
5583             {
5584                 pos.start++;
5585                 break;
5586             }
5587             
5588             skipWhites( reader, pos, true );
5589 
5590             if ( startsWith( pos, DESC_STR ) )
5591             {
5592                 elementsSeen = checkElement( elementsSeen, NormalizerElements.DESC, pos );
5593 
5594                 pos.start += DESC_STR.length();
5595                 
5596                 skipWhites( reader, pos, true );
5597 
5598                 normalizer.setDescription( getQDString( reader, pos ) );
5599             }
5600             else if ( startsWith( pos, FQCN_STR ) )
5601             {
5602                 elementsSeen = checkElement( elementsSeen, NormalizerElements.FQCN, pos );
5603 
5604                 pos.start += FQCN_STR.length();
5605                 
5606                 skipWhites( reader, pos, true );
5607 
5608                 String fqcn = getFqcn( pos );
5609                 normalizer.setFqcn( fqcn );
5610                 hasFqcn = true;
5611             }
5612             else if ( startsWith( pos, BYTECODE_STR ) )
5613             {
5614                 elementsSeen = checkElement( elementsSeen, NormalizerElements.BYTECODE, pos );
5615 
5616                 pos.start += BYTECODE_STR.length();
5617                 
5618                 skipWhites( reader, pos, true );
5619 
5620                 String byteCode = getByteCode( pos );
5621                 normalizer.setBytecode( byteCode );
5622                 hasByteCode = true;
5623             }
5624             else if ( startsWith( pos, EXTENSION_PREFIX ) )
5625             {
5626                 processExtension( reader, pos, normalizer );
5627             }
5628             else if ( startsWith( reader, pos, RPAREN ) )
5629             {
5630                 pos.start++;
5631                 break;
5632             }
5633             else
5634             {
5635                 // This is an error
5636                 throw new LdapSchemaException( I18n.err( I18n.ERR_13821_NORM_DESCRIPTION_INVALID, 
5637                     pos.lineNumber, pos.start ) );
5638             }
5639         }
5640         
5641         // Semantic checks
5642         if ( !hasFqcn )
5643         {
5644             throw new LdapSchemaException( I18n.err( I18n.ERR_13819_FQCN_REQUIRED, 
5645                 pos.lineNumber, pos.start ) );
5646         }
5647 
5648         if ( ( hasByteCode ) && ( normalizer.getBytecode().length() % 4 != 0 ) )
5649         {
5650             throw new LdapSchemaException( I18n.err( I18n.ERR_13820_BYTE_CODE_REQUIRED, 
5651                 pos.lineNumber, pos.start ) );
5652         }
5653 
5654         return normalizer;
5655     }
5656 
5657     
5658     /**
5659      * Production for Normalizer descriptions. It is fault-tolerant
5660      * against element ordering.
5661      *
5662      * <pre>
5663      * NormalizerDescription = LPAREN WSP
5664      *       numericoid                           ; object identifier
5665      *       [ SP "DESC" SP qdstring ]            ; description
5666      *       SP "FQCN" SP fqcn                    ; fully qualified class name
5667      *       [ SP "BYTECODE" SP base64 ]          ; optional base64 encoded bytecode
5668      *       extensions WSP RPAREN                ; extensions
5669      * 
5670      * base64          = *(4base64-char)
5671      * base64-char     = ALPHA / DIGIT / "+" / "/"
5672      * fqcn = fqcnComponent 1*( DOT fqcnComponent )
5673      * fqcnComponent = ???
5674      * </pre>
5675      * 
5676      * @param reader The stream reader
5677      * @param pos The position in the Schema
5678      * @param objectIdentifierMacros The set of existing Macros
5679      * @return An instance of NormalizerDescription
5680      * @throws LdapSchemaException If the schema is wrong
5681      * @throws IOException If the stream can't be read
5682      */
5683     private static NormalizerDescription parseNormalizerRelaxed( Reader reader, PosSchema pos,
5684         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 
5685             throws IOException, LdapSchemaException
5686     {
5687         // Get rid of whites, comments end empty lines
5688         skipWhites( reader, pos, false );
5689         
5690         // we must have a '('
5691         if ( pos.line.charAt( pos.start ) != LPAREN )
5692         {
5693             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
5694                 pos.lineNumber, pos.start ) );
5695         }
5696         else
5697         {
5698             pos.start++;
5699         }
5700         
5701         // Get rid of whites, comments end empty lines
5702         skipWhites( reader, pos, false );
5703         
5704         // Now, the OID. 
5705         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
5706         
5707         NormalizerDescription normalizer = new NormalizerDescription( oid );
5708         int elementsSeen = 0;
5709         
5710         while ( true )
5711         {
5712             if ( startsWith( reader, pos, RPAREN ) )
5713             {
5714                 pos.start++;
5715                 break;
5716             }
5717             
5718             skipWhites( reader, pos, true );
5719 
5720             if ( startsWith( pos, DESC_STR ) )
5721             {
5722                 elementsSeen = checkElement( elementsSeen, NormalizerElements.DESC, pos );
5723 
5724                 pos.start += DESC_STR.length();
5725                 
5726                 skipWhites( reader, pos, true );
5727 
5728                 normalizer.setDescription( getQDString( reader, pos ) );
5729             }
5730             else if ( startsWith( pos, FQCN_STR ) )
5731             {
5732                 elementsSeen = checkElement( elementsSeen, NormalizerElements.FQCN, pos );
5733 
5734                 pos.start += FQCN_STR.length();
5735                 
5736                 skipWhites( reader, pos, true );
5737 
5738                 String fqcn = getFqcn( pos );
5739                 normalizer.setFqcn( fqcn );
5740             }
5741             else if ( startsWith( pos, BYTECODE_STR ) )
5742             {
5743                 elementsSeen = checkElement( elementsSeen, NormalizerElements.BYTECODE, pos );
5744 
5745                 pos.start += BYTECODE_STR.length();
5746                 
5747                 skipWhites( reader, pos, true );
5748 
5749                 String byteCode = getByteCode( pos );
5750                 normalizer.setBytecode( byteCode );
5751             }
5752             else if ( startsWith( pos, EXTENSION_PREFIX ) )
5753             {
5754                 processExtension( reader, pos, normalizer );
5755             }
5756             else if ( startsWith( reader, pos, RPAREN ) )
5757             {
5758                 pos.start++;
5759                 break;
5760             }
5761             else
5762             {
5763                 // This is an error
5764                 throw new LdapSchemaException( I18n.err( I18n.ERR_13821_NORM_DESCRIPTION_INVALID, 
5765                     pos.lineNumber, pos.start ) );
5766             }
5767         }
5768         
5769         return normalizer;
5770     }
5771 
5772     
5773     /**
5774      * Production for matching ObjectClass descriptions. It is fault-tolerant
5775      * against element ordering.
5776      * 
5777      * <pre>
5778      * ObjectClassDescription = LPAREN WSP
5779      *   numericoid                 ; object identifier
5780      *   [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
5781      *   [ SP "DESC" SP qdstring ]  ; description
5782      *   [ SP "OBSOLETE" ]          ; not active
5783      *   [ SP "SUP" SP oids ]       ; superior object classes
5784      *   [ SP kind ]                ; kind of class
5785      *   [ SP "MUST" SP oids ]      ; attribute types
5786      *   [ SP "MAY" SP oids ]       ; attribute types
5787      *   extensions WSP RPAREN
5788      *
5789      *   kind = "ABSTRACT" / "STRUCTURAL" / "AUXILIARY"
5790      * </pre>
5791      * 
5792      * @param objectClassDescription The String containing the ObjectClassDescription
5793      * @return An instance of objectClass
5794      * @throws ParseException If the element was invalid
5795      */
5796     public ObjectClass parseObjectClass( String objectClassDescription ) throws ParseException
5797     {
5798         if ( ( objectClassDescription == null ) || Strings.isEmpty( objectClassDescription.trim() ) )
5799         {
5800             throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
5801         }
5802         
5803         try ( Reader reader = new BufferedReader( new StringReader( objectClassDescription ) ) )
5804         {
5805             PosSchema pos = new PosSchema();
5806 
5807             if ( isQuirksModeEnabled )
5808             {
5809                 return parseObjectClassRelaxed( reader, pos, objectIdentifierMacros );
5810             }
5811             else
5812             {
5813                 return parseObjectClassStrict( reader, pos, objectIdentifierMacros );
5814             }
5815         }
5816         catch ( IOException | LdapSchemaException e )
5817         {
5818             throw new ParseException( e.getMessage(), 0 );
5819         }
5820     }
5821 
5822     
5823     /**
5824      * Production for matching ObjectClass descriptions. It is fault-tolerant
5825      * against element ordering.
5826      * <pre>
5827      * ObjectClassDescription = LPAREN WSP
5828      *   numericoid                 ; object identifier
5829      *   [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
5830      *   [ SP "DESC" SP qdstring ]  ; description
5831      *   [ SP "OBSOLETE" ]          ; not active
5832      *   [ SP "SUP" SP oids ]       ; superior object classes
5833      *   [ SP kind ]                ; kind of class
5834      *   [ SP "MUST" SP oids ]      ; attribute types
5835      *   [ SP "MAY" SP oids ]       ; attribute types
5836      *   extensions WSP RPAREN
5837      *
5838      *   kind = "ABSTRACT" / "STRUCTURAL" / "AUXILIARY"
5839      * </pre>
5840      * 
5841      * @param reader The stream reader
5842      * @param pos The position in the Schema
5843      * @param objectIdentifierMacros The set of existing Macros
5844      * @return An instance of ObjectClass
5845      * @throws LdapSchemaException If the schema is wrong
5846      * @throws IOException If the stream can't be read
5847      */
5848     private static ObjectClass parseObjectClassStrict( Reader reader, PosSchema pos,
5849         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 
5850         throws IOException, LdapSchemaException
5851     {
5852         // Get rid of whites, comments end empty lines
5853         skipWhites( reader, pos, false );
5854         
5855         // we must have a '('
5856         if ( pos.line.charAt( pos.start ) != LPAREN )
5857         {
5858             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
5859                 pos.lineNumber, pos.start ) );
5860         }
5861         else
5862         {
5863             pos.start++;
5864         }
5865         
5866         // Get rid of whites, comments end empty lines
5867         skipWhites( reader, pos, false );
5868         
5869         // Now, the numeric OID
5870         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
5871         
5872         // Check that the OID is valid
5873         if ( !Oid.isOid( oid ) )
5874         {
5875             throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
5876         }
5877 
5878         ObjectClass objectClass = new ObjectClass( oid );
5879         int elementsSeen = 0;
5880         
5881         while ( true )
5882         {
5883             if ( startsWith( reader, pos, RPAREN ) )
5884             {
5885                 pos.start++;
5886                 break;
5887             }
5888             
5889             skipWhites( reader, pos, true );
5890 
5891             if ( startsWith( pos, NAME_STR ) )
5892             {
5893                 elementsSeen = checkElement( elementsSeen, ObjectClassElements.NAME, pos );
5894 
5895                 pos.start += NAME_STR.length();
5896                 
5897                 skipWhites( reader, pos, true );
5898 
5899                 List<String> names = getQDescrs( reader, pos, STRICT );
5900                 objectClass.setNames( names );
5901             }
5902             else if ( startsWith( pos, DESC_STR ) )
5903             {
5904                 elementsSeen = checkElement( elementsSeen, ObjectClassElements.DESC, pos );
5905                 
5906                 pos.start += DESC_STR.length();
5907                 
5908                 skipWhites( reader, pos, true );
5909 
5910                 objectClass.setDescription( getQDString( reader, pos ) );
5911             }
5912             else if ( startsWith( pos, OBSOLETE_STR ) )
5913             {
5914                 elementsSeen = checkElement( elementsSeen, ObjectClassElements.OBSOLETE, pos );
5915                 
5916                 pos.start += OBSOLETE_STR.length();
5917                 
5918                 objectClass.setObsolete( true );
5919             }
5920             else if ( startsWith( pos, SUP_STR ) )
5921             {
5922                 elementsSeen = checkElement( elementsSeen, ObjectClassElements.SUP, pos );
5923                 
5924                 pos.start += SUP_STR.length();
5925                 
5926                 skipWhites( reader, pos, true );
5927                 
5928                 List<String> superiorOids = getOidsStrict( reader, pos );
5929 
5930                 objectClass.setSuperiorOids( superiorOids );
5931             }
5932             else if ( startsWith( pos, ABSTRACT_STR ) )
5933             {
5934                 elementsSeen = checkElement( elementsSeen, ObjectClassElements.ABSTRACT, pos );
5935                 
5936                 pos.start += ABSTRACT_STR.length();
5937                 
5938                 objectClass.setType( ObjectClassTypeEnum.ABSTRACT );
5939             }
5940             else if ( startsWith( pos, STRUCTURAL_STR ) )
5941             {
5942                 elementsSeen = checkElement( elementsSeen, ObjectClassElements.STRUCTURAL, pos );
5943                 
5944                 pos.start += STRUCTURAL_STR.length();
5945                 
5946                 objectClass.setType( ObjectClassTypeEnum.STRUCTURAL );
5947             }
5948             else if ( startsWith( pos, AUXILIARY_STR ) )
5949             {
5950                 elementsSeen = checkElement( elementsSeen, ObjectClassElements.AUXILIARY, pos );
5951                 
5952                 pos.start += AUXILIARY_STR.length();
5953                 
5954                 objectClass.setType( ObjectClassTypeEnum.AUXILIARY );
5955             }
5956             else if ( startsWith( pos, MUST_STR ) )
5957             {
5958                 elementsSeen = checkElement( elementsSeen, ObjectClassElements.MUST, pos );
5959                 
5960                 pos.start += MUST_STR.length();
5961                 
5962                 skipWhites( reader, pos, true );
5963                 
5964                 List<String> mustAttributeTypes = getOidsStrict( reader, pos );
5965                 objectClass.setMustAttributeTypeOids( mustAttributeTypes );
5966             }
5967             else if ( startsWith( pos, MAY_STR ) )
5968             {
5969                 elementsSeen = checkElement( elementsSeen, ObjectClassElements.MAY, pos );
5970                 
5971                 pos.start += MAY_STR.length();
5972                 
5973                 skipWhites( reader, pos, true );
5974                 
5975                 List<String> mayAttributeTypes = getOidsStrict( reader, pos );
5976                 objectClass.setMayAttributeTypeOids( mayAttributeTypes );
5977             }
5978             else if ( startsWith( pos, EXTENSION_PREFIX ) )
5979             {
5980                 processExtension( reader, pos, objectClass );
5981             }
5982             else if ( startsWith( reader, pos, RPAREN ) )
5983             {
5984                 pos.start++;
5985                 break;
5986             }
5987             else
5988             {
5989                 // This is an error
5990                 throw new LdapSchemaException( I18n.err( I18n.ERR_13803_OC_DESCRIPTION_INVALID, 
5991                     pos.lineNumber, pos.start ) );
5992             }
5993         }
5994 
5995         pos.start++;
5996         
5997         return objectClass;
5998     }
5999 
6000     
6001     /**
6002      * Production for matching ObjectClass descriptions. It is fault-tolerant
6003      * against element ordering.
6004      * <pre>
6005      * ObjectClassDescription = LPAREN WSP
6006      *   numericoid                 ; object identifier
6007      *   [ SP "NAME" SP qdescrs ]   ; short names (descriptors)
6008      *   [ SP "DESC" SP qdstring ]  ; description
6009      *   [ SP "OBSOLETE" ]          ; not active
6010      *   [ SP "SUP" SP oids ]       ; superior object classes
6011      *   [ SP kind ]                ; kind of class
6012      *   [ SP "MUST" SP oids ]      ; attribute types
6013      *   [ SP "MAY" SP oids ]       ; attribute types
6014      *   extensions WSP RPAREN
6015      *
6016      *   kind = "ABSTRACT" / "STRUCTURAL" / "AUXILIARY"
6017      * </pre>
6018      * 
6019      * @param reader The stream reader
6020      * @param pos The position in the Schema
6021      * @param objectIdentifierMacros The set of existing Macros
6022      * @return An instance of ObjectClass
6023      * @throws LdapSchemaException If the schema is wrong
6024      * @throws IOException If the stream can't be read
6025      */
6026     private static ObjectClass parseObjectClassRelaxed( Reader reader, PosSchema pos,
6027         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 
6028             throws IOException, LdapSchemaException
6029     {
6030         // Get rid of whites, comments end empty lines
6031         skipWhites( reader, pos, false );
6032         
6033         // we must have a '('
6034         if ( pos.line.charAt( pos.start ) != LPAREN )
6035         {
6036             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
6037                 pos.lineNumber, pos.start ) );
6038         }
6039         else
6040         {
6041             pos.start++;
6042         }
6043         
6044         // Get rid of whites, comments end empty lines
6045         skipWhites( reader, pos, false );
6046         
6047         // Now, the numeric OID
6048         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
6049         
6050         ObjectClass objectClass = new ObjectClass( oid );
6051         int elementsSeen = 0;
6052         
6053         while ( true )
6054         {
6055             if ( startsWith( reader, pos, RPAREN ) )
6056             {
6057                 pos.start++;
6058                 break;
6059             }
6060             
6061             skipWhites( reader, pos, true );
6062 
6063             if ( startsWith( pos, NAME_STR ) )
6064             {
6065                 elementsSeen = checkElement( elementsSeen, ObjectClassElements.NAME, pos );
6066 
6067                 pos.start += NAME_STR.length();
6068                 
6069                 skipWhites( reader, pos, true );
6070 
6071                 List<String> names = getQDescrs( reader, pos, RELAXED );
6072 
6073                 objectClass.setNames( names );
6074             }
6075             else if ( startsWith( pos, DESC_STR ) )
6076             {
6077                 elementsSeen = checkElement( elementsSeen, ObjectClassElements.DESC, pos );
6078                 
6079                 pos.start += DESC_STR.length();
6080                 
6081                 skipWhites( reader, pos, true );
6082 
6083                 objectClass.setDescription( getQDString( reader, pos ) );
6084             }
6085             else if ( startsWith( pos, OBSOLETE_STR ) )
6086             {
6087                 elementsSeen = checkElement( elementsSeen, ObjectClassElements.OBSOLETE, pos );
6088                 
6089                 pos.start += OBSOLETE_STR.length();
6090                 
6091                 objectClass.setObsolete( true );
6092             }
6093             else if ( startsWith( pos, SUP_STR ) )
6094             {
6095                 elementsSeen = checkElement( elementsSeen, ObjectClassElements.SUP, pos );
6096                 
6097                 pos.start += SUP_STR.length();
6098                 
6099                 skipWhites( reader, pos, true );
6100                 
6101                 List<String> superiorOids = getOidsRelaxed( reader, pos );
6102 
6103                 objectClass.setSuperiorOids( superiorOids );
6104             }
6105             else if ( startsWith( pos, ABSTRACT_STR ) )
6106             {
6107                 elementsSeen = checkElement( elementsSeen, ObjectClassElements.ABSTRACT, pos );
6108                 
6109                 pos.start += ABSTRACT_STR.length();
6110                 
6111                 objectClass.setType( ObjectClassTypeEnum.ABSTRACT );
6112             }
6113             else if ( startsWith( pos, STRUCTURAL_STR ) )
6114             {
6115                 elementsSeen = checkElement( elementsSeen, ObjectClassElements.STRUCTURAL, pos );
6116                 
6117                 pos.start += STRUCTURAL_STR.length();
6118                 
6119                 objectClass.setType( ObjectClassTypeEnum.STRUCTURAL );
6120             }
6121             else if ( startsWith( pos, AUXILIARY_STR ) )
6122             {
6123                 elementsSeen = checkElement( elementsSeen, ObjectClassElements.AUXILIARY, pos );
6124                 
6125                 pos.start += AUXILIARY_STR.length();
6126                 
6127                 objectClass.setType( ObjectClassTypeEnum.AUXILIARY );
6128             }
6129             else if ( startsWith( pos, MUST_STR ) )
6130             {
6131                 elementsSeen = checkElement( elementsSeen, ObjectClassElements.MUST, pos );
6132                 
6133                 pos.start += MUST_STR.length();
6134                 
6135                 skipWhites( reader, pos, true );
6136                 
6137                 List<String> mustAttributeTypes = getOidsRelaxed( reader, pos );
6138                 objectClass.setMustAttributeTypeOids( mustAttributeTypes );
6139             }
6140             else if ( startsWith( pos, MAY_STR ) )
6141             {
6142                 elementsSeen = checkElement( elementsSeen, ObjectClassElements.MAY, pos );
6143                 
6144                 pos.start += MAY_STR.length();
6145                 
6146                 skipWhites( reader, pos, true );
6147                 
6148                 List<String> mayAttributeTypes = getOidsRelaxed( reader, pos );
6149                 objectClass.setMayAttributeTypeOids( mayAttributeTypes );
6150             }
6151             else if ( startsWith( pos, EXTENSION_PREFIX ) )
6152             {
6153                 processExtension( reader, pos, objectClass );
6154             }
6155             else if ( startsWith( reader, pos, RPAREN ) )
6156             {
6157                 pos.start++;
6158                 break;
6159             }
6160             else
6161             {
6162                 // This is an error
6163                 throw new LdapSchemaException( I18n.err( I18n.ERR_13803_OC_DESCRIPTION_INVALID, 
6164                     pos.lineNumber, pos.start ) );
6165             }
6166         }
6167 
6168         pos.start++;
6169         
6170         return objectClass;
6171     }
6172 
6173     
6174     /**
6175      * Production for SyntaxChecker descriptions. It is fault-tolerant
6176      * against element ordering.
6177      *
6178      * <pre>
6179      * SyntaxCheckerDescription = LPAREN WSP
6180      *       numericoid                           ; object identifier
6181      *       [ SP "DESC" SP qdstring ]            ; description
6182      *       SP "FQCN" SP fqcn                    ; fully qualified class name
6183      *       [ SP "BYTECODE" SP base64 ]          ; optional base64 encoded bytecode
6184      *       extensions WSP RPAREN                ; extensions
6185      * 
6186      * base64          = *(4base64-char)
6187      * base64-char     = ALPHA / DIGIT / "+" / "/"
6188      * fqcn = fqcnComponent 1*( DOT fqcnComponent )
6189      * fqcnComponent = ???
6190      * </pre>
6191      * 
6192      * @param syntaxCheckerDescription The String containing the SyntaxCheckerDescription
6193      * @return An instance of SyntaxCheckerDescription
6194      * @throws ParseException If the element was invalid
6195      */
6196     public SyntaxCheckerDescription parseSyntaxChecker( String syntaxCheckerDescription ) throws ParseException
6197     {
6198         if ( ( syntaxCheckerDescription == null ) || Strings.isEmpty( syntaxCheckerDescription.trim() ) )
6199         {
6200             throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
6201         }
6202         
6203         try ( Reader reader = new BufferedReader( new StringReader( syntaxCheckerDescription ) ) )
6204         {
6205             PosSchema pos = new PosSchema();
6206 
6207             if ( isQuirksModeEnabled )
6208             {
6209                 return parseSyntaxCheckerRelaxed( reader, pos, objectIdentifierMacros );
6210             }
6211             else
6212             {
6213                 return parseSyntaxCheckerStrict( reader, pos, objectIdentifierMacros );
6214             }
6215         }
6216         catch ( IOException | LdapSchemaException e )
6217         {
6218             throw new ParseException( e.getMessage(), 0 );
6219         }
6220     }
6221 
6222     
6223     /**
6224      * Production for SyntaxChecker descriptions. It is fault-tolerant
6225      * against element ordering.
6226      *
6227      * <pre>
6228      * SyntaxCheckerDescription = LPAREN WSP
6229      *       numericoid                           ; object identifier
6230      *       [ SP "DESC" SP qdstring ]            ; description
6231      *       SP "FQCN" SP fqcn                    ; fully qualified class name
6232      *       [ SP "BYTECODE" SP base64 ]          ; optional base64 encoded bytecode
6233      *       extensions WSP RPAREN                ; extensions
6234      * 
6235      * base64          = *(4base64-char)
6236      * base64-char     = ALPHA / DIGIT / "+" / "/"
6237      * fqcn = fqcnComponent 1*( DOT fqcnComponent )
6238      * fqcnComponent = ???
6239      * </pre>
6240      * 
6241      * @param reader The stream reader
6242      * @param pos The position in the Schema
6243      * @param objectIdentifierMacros The set of existing Macros
6244      * @return An instance of SyntaxCheckerDescription
6245      * @throws LdapSchemaException If the schema is wrong
6246      * @throws IOException If the stream can't be read
6247      */
6248     private static SyntaxCheckerDescription parseSyntaxCheckerStrict( Reader reader, PosSchema pos,
6249         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException
6250     {
6251         // Get rid of whites, comments end empty lines
6252         skipWhites( reader, pos, false );
6253         
6254         // we must have a '('
6255         if ( pos.line.charAt( pos.start ) != LPAREN )
6256         {
6257             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
6258                 pos.lineNumber, pos.start ) );
6259         }
6260         else
6261         {
6262             pos.start++;
6263         }
6264         
6265         // Get rid of whites, comments end empty lines
6266         skipWhites( reader, pos, false );
6267         
6268         // Now, the OID. 
6269         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
6270         
6271         // Check that the OID is valid
6272         if ( !Oid.isOid( oid ) )
6273         {
6274             throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
6275         }
6276         
6277         SyntaxCheckerDescription syntaxChecker = new SyntaxCheckerDescription( oid );
6278         int elementsSeen = 0;
6279         boolean hasFqcn = false;
6280         boolean hasByteCode = false;
6281         
6282         while ( true )
6283         {
6284             if ( startsWith( reader, pos, RPAREN ) )
6285             {
6286                 pos.start++;
6287                 break;
6288             }
6289             
6290             skipWhites( reader, pos, true );
6291 
6292             if ( startsWith( pos, DESC_STR ) )
6293             {
6294                 elementsSeen = checkElement( elementsSeen, SyntaxCheckerElements.DESC, pos );
6295 
6296                 pos.start += DESC_STR.length();
6297                 
6298                 skipWhites( reader, pos, true );
6299 
6300                 syntaxChecker.setDescription( getQDString( reader, pos ) );
6301             }
6302             else if ( startsWith( pos, FQCN_STR ) )
6303             {
6304                 elementsSeen = checkElement( elementsSeen, SyntaxCheckerElements.FQCN, pos );
6305 
6306                 pos.start += FQCN_STR.length();
6307                 
6308                 skipWhites( reader, pos, true );
6309 
6310                 String fqcn = getFqcn( pos );
6311                 syntaxChecker.setFqcn( fqcn );
6312                 hasFqcn = true;
6313             }
6314             else if ( startsWith( pos, BYTECODE_STR ) )
6315             {
6316                 elementsSeen = checkElement( elementsSeen, SyntaxCheckerElements.BYTECODE, pos );
6317 
6318                 pos.start += BYTECODE_STR.length();
6319                 
6320                 skipWhites( reader, pos, true );
6321 
6322                 String byteCode = getByteCode( pos );
6323                 syntaxChecker.setBytecode( byteCode );
6324                 hasByteCode = true;
6325             }
6326             else if ( startsWith( pos, EXTENSION_PREFIX ) )
6327             {
6328                 processExtension( reader, pos, syntaxChecker );
6329             }
6330             else if ( startsWith( reader, pos, RPAREN ) )
6331             {
6332                 pos.start++;
6333                 break;
6334             }
6335             else
6336             {
6337                 // This is an error
6338                 throw new LdapSchemaException( I18n.err( I18n.ERR_13826_SC_DESCRIPTION_INVALID, 
6339                     pos.lineNumber, pos.start ) );
6340             }
6341         }
6342         
6343         // Semantic checks
6344         if ( !hasFqcn )
6345         {
6346             throw new LdapSchemaException( I18n.err( I18n.ERR_13819_FQCN_REQUIRED, 
6347                 pos.lineNumber, pos.start ) );
6348         }
6349 
6350         if ( ( hasByteCode ) && ( syntaxChecker.getBytecode().length() % 4 != 0 ) )
6351         {
6352             throw new LdapSchemaException( I18n.err( I18n.ERR_13820_BYTE_CODE_REQUIRED, 
6353                 pos.lineNumber, pos.start ) );
6354         }
6355 
6356         return syntaxChecker;
6357     }
6358 
6359     
6360     /**
6361      * Production for SyntaxChecker descriptions. It is fault-tolerant
6362      * against element ordering.
6363      *
6364      * <pre>
6365      * SyntaxCheckerDescription = LPAREN WSP
6366      *       numericoid                           ; object identifier
6367      *       [ SP "DESC" SP qdstring ]            ; description
6368      *       SP "FQCN" SP fqcn                    ; fully qualified class name
6369      *       [ SP "BYTECODE" SP base64 ]          ; optional base64 encoded bytecode
6370      *       extensions WSP RPAREN                ; extensions
6371      * 
6372      * base64          = *(4base64-char)
6373      * base64-char     = ALPHA / DIGIT / "+" / "/"
6374      * fqcn = fqcnComponent 1*( DOT fqcnComponent )
6375      * fqcnComponent = ???
6376      * </pre>
6377      * 
6378      * @param reader The stream reader
6379      * @param pos The position in the Schema
6380      * @param objectIdentifierMacros The set of existing Macros
6381      * @return An instance of SyntaxCheckerDescription
6382      * @throws LdapSchemaException If the schema is wrong
6383      * @throws IOException If the stream can't be read
6384      */
6385     private static SyntaxCheckerDescription parseSyntaxCheckerRelaxed( Reader reader, PosSchema pos,
6386         Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 
6387             throws IOException, LdapSchemaException
6388     {
6389         // Get rid of whites, comments end empty lines
6390         skipWhites( reader, pos, false );
6391         
6392         // we must have a '('
6393         if ( pos.line.charAt( pos.start ) != LPAREN )
6394         {
6395             throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 
6396                 pos.lineNumber, pos.start ) );
6397         }
6398         else
6399         {
6400             pos.start++;
6401         }
6402         
6403         // Get rid of whites, comments end empty lines
6404         skipWhites( reader, pos, false );
6405         
6406         // Now, the OID. 
6407         String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
6408         
6409         SyntaxCheckerDescription syntaxChecker = new SyntaxCheckerDescription( oid );
6410         int elementsSeen = 0;
6411         
6412         while ( true )
6413         {
6414             if ( startsWith( reader, pos, RPAREN ) )
6415             {
6416                 pos.start++;
6417                 break;
6418             }
6419             
6420             skipWhites( reader, pos, true );
6421 
6422             if ( startsWith( pos, DESC_STR ) )
6423             {
6424                 elementsSeen = checkElement( elementsSeen, SyntaxCheckerElements.DESC, pos );
6425 
6426                 pos.start += DESC_STR.length();
6427                 
6428                 skipWhites( reader, pos, true );
6429 
6430                 syntaxChecker.setDescription( getQDString( reader, pos ) );
6431             }
6432             else if ( startsWith( pos, FQCN_STR ) )
6433             {
6434                 elementsSeen = checkElement( elementsSeen, SyntaxCheckerElements.FQCN, pos );
6435 
6436                 pos.start += FQCN_STR.length();
6437                 
6438                 skipWhites( reader, pos, true );
6439 
6440                 String fqcn = getFqcn( pos );
6441                 syntaxChecker.setFqcn( fqcn );
6442             }
6443             else if ( startsWith( pos, BYTECODE_STR ) )
6444             {
6445                 elementsSeen = checkElement( elementsSeen, SyntaxCheckerElements.BYTECODE, pos );
6446 
6447                 pos.start += BYTECODE_STR.length();
6448                 
6449                 skipWhites( reader, pos, true );
6450 
6451                 String byteCode = getByteCode( pos );
6452                 syntaxChecker.setBytecode( byteCode );
6453             }
6454             else if ( startsWith( pos, EXTENSION_PREFIX ) )
6455             {
6456                 processExtension( reader, pos, syntaxChecker );
6457             }
6458             else if ( startsWith( reader, pos, RPAREN ) )
6459             {
6460                 pos.start++;
6461                 break;
6462             }
6463             else
6464             {
6465                 // This is an error
6466                 throw new LdapSchemaException( I18n.err( I18n.ERR_13826_SC_DESCRIPTION_INVALID, 
6467                     pos.lineNumber, pos.start ) );
6468             }
6469         }
6470         
6471         return syntaxChecker;
6472     }
6473 
6474     
6475     /**
6476      * Process OpenLDAP macros, like : objectidentifier DUAConfSchemaOID 1.3.6.1.4.1.11.1.3.1.
6477      * 
6478      * <pre>
6479      * objectidentifier ::= 'objectidentifier' descr SP+ macroOid
6480      * descr             ::= ALPHA ( ALPHA | DIGIT | HYPHEN )*
6481      * macroOid         ::= (descr ':')? oid
6482      * </pre>
6483      * 
6484      * @param reader The stream reader
6485      * @param pos The position in the Schema
6486      * @throws LdapSchemaException If something went wrong in the schema
6487      * @throws IOException If the stream can't be read
6488      */
6489     private void processObjectIdentifier( Reader reader, PosSchema pos ) throws IOException, LdapSchemaException
6490     {
6491         // Get rid of whites, comments end empty lines
6492         skipWhites( reader, pos, false );
6493         
6494         // Now, the name
6495         String name = getDescrStrict( pos );
6496         
6497         OpenLdapObjectIdentifierMacro macro = new OpenLdapObjectIdentifierMacro();
6498         
6499         skipWhites( reader, pos, true );
6500 
6501         // Get the descr, if any
6502         if ( isEmpty( pos ) )
6503         {
6504             throw new LdapSchemaException( I18n.err( I18n.ERR_13805_OBJECT_IDENTIFIER_INVALID_OID, 
6505                 pos.lineNumber, pos.start ) );
6506         }
6507         
6508         if ( isAlpha( pos ) )
6509         {
6510             // A macro
6511             String descr = getMacro( pos );
6512             
6513             if ( isEmpty( pos ) )
6514             {
6515                 throw new LdapSchemaException( I18n.err( I18n.ERR_13804_OBJECT_IDENTIFIER_HAS_NO_OID, 
6516                     pos.lineNumber, pos.start ) );
6517             }
6518             
6519             if ( startsWith( reader, pos, COLON ) )
6520             {
6521                 pos.start++;
6522                 
6523                 // Now, the OID
6524                 String numericOid = getPartialNumericOid( pos );
6525                 String realOid = objectIdentifierMacros.get( descr ).getRawOidOrNameSuffix() + DOT + numericOid;
6526                 macro.setName( name );
6527                 macro.setRawOidOrNameSuffix( realOid );
6528                 
6529                 objectIdentifierMacros.put( name, macro );
6530             }
6531         }
6532         else if ( isDigit( pos ) )
6533         {
6534             // An oid
6535             String numericOid = getNumericOid( pos );
6536             macro.setRawOidOrNameSuffix( numericOid );
6537             macro.setName( name );
6538             
6539             objectIdentifierMacros.put( name, macro );
6540         }
6541         else
6542         {
6543             throw new LdapSchemaException( I18n.err( I18n.ERR_13805_OBJECT_IDENTIFIER_INVALID_OID, 
6544                 pos.lineNumber, pos.start ) );
6545         }
6546     }
6547     
6548     
6549     /**
6550      * Reads an entry in a ldif buffer, and returns the resulting lines, without
6551      * comments, and unfolded.
6552      *
6553      * The lines represent *one* entry.
6554      *
6555      * @param reader The stream reader
6556      * @throws LdapSchemaException If something went wrong in the schema
6557      * @throws IOException If the stream can't be read
6558      */
6559     public void parse( Reader reader ) throws LdapSchemaException, IOException
6560     {
6561         PosSchema pos = new PosSchema();
6562 
6563         while ( true )
6564         {
6565             // Always move forward to the next element, skipping whites, NL and comments
6566             skipWhites( reader, pos, false );
6567             
6568             if ( pos.line == null )
6569             {
6570                 // The end, get out
6571                 break;
6572             }
6573             
6574             // Ok, we have something which must be one of openLdapObjectIdentifier( "objectidentifier" ), 
6575             // openLdapAttributeType ( "attributetype" )  or openLdapObjectClass ( "objectclass" )
6576             if ( startsWith( pos, "objectidentifier" ) )
6577             {
6578                 pos.start += "objectidentifier".length();
6579                 
6580                 processObjectIdentifier( reader, pos );
6581             }
6582             else if ( startsWith( pos, "attributetype" ) )
6583             {
6584                 pos.start += "attributetype".length();
6585                 
6586                 AttributeType attributeType = parseAttributeTypeStrict( reader, pos, objectIdentifierMacros );
6587                 schemaDescriptions.add( attributeType );
6588             }
6589             else if ( startsWith( pos, "objectclass" ) )
6590             {
6591                 pos.start += "objectclass".length();
6592                 
6593                 ObjectClass objectClass = parseObjectClassStrict( reader, pos, objectIdentifierMacros );
6594                 schemaDescriptions.add( objectClass );
6595             }
6596             else
6597             {
6598                 // This is an error
6599                 throw new LdapSchemaException( I18n.err( I18n.ERR_13806_UNEXPECTED_ELEMENT_READ, 
6600                     pos.line.substring( pos.start ), pos.lineNumber, pos.start ) );
6601             }
6602         }
6603     }
6604 
6605 
6606     /**
6607      * Parses a file of OpenLDAP schemaObject elements/objects. Default charset is used.
6608      *
6609      * @param schemaFile a file of schema objects
6610      * @throws ParseException If the schemaObject can't be parsed
6611      */
6612     public void parse( File schemaFile ) throws ParseException
6613     {
6614         try ( InputStream is = Files.newInputStream( Paths.get( schemaFile.getPath() ) ) )
6615         {
6616             try ( Reader reader = new BufferedReader( new InputStreamReader( is, Charset.defaultCharset() ) ) )
6617             {
6618                 parse( reader );
6619                 afterParse();
6620             }
6621             catch ( LdapSchemaException | IOException e )
6622             {
6623                 throw new ParseException( e.getMessage(), 0 );
6624             }
6625         }
6626         catch ( IOException e )
6627         {
6628             String msg = I18n.err( I18n.ERR_13443_CANNOT_FIND_FILE, schemaFile.getAbsoluteFile() );
6629             LOG.error( msg );
6630             throw new ParseException( e.getMessage(), 0 );
6631         }
6632     }
6633 
6634 
6635     /**
6636      * Checks if object identifier macros should be resolved.
6637      * 
6638      * @return true, object identifier macros should be resolved.
6639      */
6640     public boolean isResolveObjectIdentifierMacros()
6641     {
6642         return isResolveObjectIdentifierMacros;
6643     }
6644 
6645 
6646     /**
6647      * Sets if object identifier macros should be resolved.
6648      * 
6649      * @param resolveObjectIdentifierMacros true if object identifier macros should be resolved
6650      */
6651     public void setResolveObjectIdentifierMacros( boolean resolveObjectIdentifierMacros )
6652     {
6653         this.isResolveObjectIdentifierMacros = resolveObjectIdentifierMacros;
6654     }
6655 
6656     /**
6657      * Checks if quirks mode is enabled.
6658      * 
6659      * @return true, if is quirks mode is enabled
6660      */
6661     public boolean isQuirksMode()
6662     {
6663         return isQuirksModeEnabled;
6664     }
6665 
6666 
6667     /**
6668      * Sets the quirks mode. 
6669      * 
6670      * If enabled the parser accepts non-numeric OIDs and some 
6671      * special characters in descriptions.
6672      * 
6673      * @param enabled the new quirks mode
6674      */
6675     public void setQuirksMode( boolean enabled )
6676     {
6677         isQuirksModeEnabled = enabled;
6678     }
6679 }