001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 * 
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 * 
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 * 
019 */
020package org.apache.directory.api.ldap.model.schema.parsers;
021
022
023import java.io.BufferedReader;
024import java.io.File;
025import java.io.IOException;
026import java.io.InputStream;
027import java.io.InputStreamReader;
028import java.io.Reader;
029import java.io.StringReader;
030import java.nio.charset.Charset;
031import java.nio.file.Files;
032import java.nio.file.Paths;
033import java.text.ParseException;
034import java.util.ArrayList;
035import java.util.HashMap;
036import java.util.List;
037import java.util.Map;
038
039import org.apache.directory.api.asn1.util.Oid;
040import org.apache.directory.api.i18n.I18n;
041import org.apache.directory.api.ldap.model.exception.LdapSchemaException;
042import org.apache.directory.api.ldap.model.schema.AttributeType;
043import org.apache.directory.api.ldap.model.schema.DitContentRule;
044import org.apache.directory.api.ldap.model.schema.DitStructureRule;
045import org.apache.directory.api.ldap.model.schema.LdapSyntax;
046import org.apache.directory.api.ldap.model.schema.MatchingRule;
047import org.apache.directory.api.ldap.model.schema.MatchingRuleUse;
048import org.apache.directory.api.ldap.model.schema.ObjectClass;
049import org.apache.directory.api.ldap.model.schema.NameForm;
050import org.apache.directory.api.ldap.model.schema.ObjectClassTypeEnum;
051import org.apache.directory.api.ldap.model.schema.SchemaObject;
052import org.apache.directory.api.ldap.model.schema.UsageEnum;
053import org.apache.directory.api.ldap.model.schema.syntaxCheckers.OpenLdapObjectIdentifierMacro;
054import org.apache.directory.api.util.Strings;
055import org.slf4j.Logger;
056import org.slf4j.LoggerFactory;
057
058/**
059 * A reusable wrapper for hand parser OpenLDAP schema.
060 *
061 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
062 */
063public class OpenLdapSchemaParser
064{
065    /** The LoggerFactory used by this class */
066    protected static final Logger LOG = LoggerFactory.getLogger( OpenLdapSchemaParser.class );
067
068    /** A flag used to tell the parser if it should be strict or not */
069    private boolean isQuirksModeEnabled = false;
070
071    /** the number of the current line being parsed by the reader */
072    protected int lineNumber;
073
074    /** The list of parsed schema descriptions */
075    private List<Object> schemaDescriptions = new ArrayList<>();
076
077    /** The list of attribute type, initialized by splitParsedSchemaDescriptions() */
078    private List<AttributeType> attributeTypes;
079
080    /** The list of object classes, initialized by splitParsedSchemaDescriptions()*/
081    private List<ObjectClass> objectClasses;
082
083    /** The map of object identifier macros, initialized by splitParsedSchemaDescriptions()*/
084    private Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros = new HashMap<>();
085    
086    /** Some contant strings used in descriptions */
087    private static final String APPLIES_STR                 = "APPLIES";
088    private static final String ABSTRACT_STR                = "ABSTRACT";
089    private static final String AUX_STR                     = "AUX";
090    private static final String AUXILIARY_STR               = "AUXILIARY";
091    private static final String BYTECODE_STR                = "BYTECODE";
092    private static final String COLLECTIVE_STR              = "COLLECTIVE";
093    private static final String DESC_STR                    = "DESC";
094    private static final String EQUALITY_STR                = "EQUALITY";
095    private static final String FORM_STR                    = "FORM";
096    private static final String FQCN_STR                    = "FQCN";
097    private static final String MAY_STR                     = "MAY";
098    private static final String MUST_STR                    = "MUST";
099    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}