You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by el...@apache.org on 2018/05/15 22:31:04 UTC
[directory-ldap-api] 02/06: Added a hand-crafted OpenLDAP schema
parser,
which is 40x faster than the antlr based one. It only supports AT and OC
atm.
This is an automated email from the ASF dual-hosted git repository.
elecharny pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/directory-ldap-api.git
commit cefbe9ea42d9b9abd7eb5854139817a83b3c7a45
Author: Emmanuel Lécharny <el...@symas.com>
AuthorDate: Wed May 2 10:58:51 2018 +0200
Added a hand-crafted OpenLDAP schema parser, which is 40x faster than
the antlr based one.
It only supports AT and OC atm.
---
.../java/org/apache/directory/api/i18n/I18n.java | 29 +
.../apache/directory/api/i18n/errors.properties | 28 +
ldap/model/src/checkstyle/suppressions.xml | 1 +
.../schema/parsers/FastOpenLdapSchemaParser.java | 2266 ++++++++++++++++++++
...Test.java => FastOpenLdapSchemaParserTest.java} | 31 +-
.../schema/parsers/OpenLdapSchemaParserTest.java | 22 +
6 files changed, 2373 insertions(+), 4 deletions(-)
diff --git a/i18n/src/main/java/org/apache/directory/api/i18n/I18n.java b/i18n/src/main/java/org/apache/directory/api/i18n/I18n.java
index 5027889..4c541f3 100644
--- a/i18n/src/main/java/org/apache/directory/api/i18n/I18n.java
+++ b/i18n/src/main/java/org/apache/directory/api/i18n/I18n.java
@@ -807,6 +807,35 @@ public enum I18n
ERR_13778_COLLECTIVE_NOT_ALLOWED_IN_MUST( "ERR_13778_COLLECTIVE_NOT_ALLOWED_IN_MUST" ),
ERR_13779_COLLECTIVE_NOT_ALLOWED_IN_MAY( "ERR_13779_COLLECTIVE_NOT_ALLOWED_IN_MAY" ),
+ ERR_13780_AT_DESCRIPTION_HAS_ELEMENT_TWICE( "ERR_13780_AT_DESCRIPTION_HAS_ELEMENT_TWICE" ),
+ ERR_13781_OC_DESCRIPTION_HAS_ELEMENT_TWICE( "ERR_13781_OC_DESCRIPTION_HAS_ELEMENT_TWICE" ),
+ ERR_13782_END_OF_FILE( "ERR_13782_END_OF_FILE" ),
+ ERR_13783_SPACE_EXPECTED( "ERR_13783_SPACE_EXPECTED" ),
+ ERR_13784_BAD_OID_TWO_ZEROES( "ERR_13785_BAD_OID_CONSECUTIVE_DOTS" ),
+ ERR_13785_BAD_OID_CONSECUTIVE_DOTS( "" ),
+ ERR_13786_BAD_OID_DOT_AT_THE_END( "ERR_13786_BAD_OID_DOT_AT_THE_END" ),
+ ERR_13787_OID_EXPECTED( "ERR_13787_OID_EXPECTED" ),
+ ERR_13788_LEAD_KEY_CHAR_EXPECTED( "ERR_13788_LEAD_KEY_CHAR_EXPECTED" ),
+ ERR_13789_SIMPLE_QUOTE_EXPECTED_AT_START( "ERR_13789_SIMPLE_QUOTE_EXPECTED_AT_START" ),
+ ERR_13790_NOT_A_KEYSTRING( "ERR_13790_NOT_A_KEYSTRING" ),
+ ERR_13791_KEYCHAR_EXPECTED( "ERR_13791_KEYCHAR_EXPECTED" ),
+ ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END( "ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END" ),
+ ERR_13793_NO_CLOSING_PAREN( "ERR_13793_NO_CLOSING_PAREN" ),
+ ERR_13794_MORE_OIDS_EXPECTED( "ERR_13794_MORE_OIDS_EXPECTED" ),
+ ERR_13795_OPENED_BRACKET_NOT_CLOSED( "ERR_13795_OPENED_BRACKET_NOT_CLOSED" ),
+ ERR_13796_USAGE_EXPECTED( "ERR_13796_USAGE_EXPECTED" ),
+ ERR_13797_USAGE_UNKNOWN( "ERR_13797_USAGE_UNKNOWN" ),
+ ERR_13798_AT_DESCRIPTION_INVALID( "ERR_13798_AT_DESCRIPTION_INVALID" ),
+ ERR_13799_SYNTAX_OR_SUP_REQUIRED( "ERR_13799_SYNTAX_OR_SUP_REQUIRED" ),
+ ERR_13800_COLLECTIVE_REQUIRES_USER_APPLICATION( "ERR_13800_COLLECTIVE_REQUIRES_USER_APPLICATION" ),
+ ERR_13801_NO_USER_MOD_REQUIRE_OPERATIONAL( "ERR_13801_NO_USER_MOD_REQUIRE_OPERATIONAL" ),
+ ERR_13802_EXTENSION_SHOULD_START_WITH_X( "ERR_13802_EXTENSION_SHOULD_START_WITH_X" ),
+ ERR_13803_OC_DESCRIPTION_INVALID( "ERR_13803_OC_DESCRIPTION_INVALID" ),
+ ERR_13804_OBJECT_IDENTIFIER_HAS_NO_OID( "ERR_13804_OBJECT_IDENTIFIER_HAS_NO_OID" ),
+ ERR_13805_OBJECT_IDENTIFIER_INVALID_OID( "ERR_13805_OBJECT_IDENTIFIER_INVALID_OID" ),
+ ERR_13806_UNEXPECTED_ELEMENT_READ( "ERR_13806_UNEXPECTED_ELEMENT_READ" ),
+
+
// api-ldap-net-mina 14000 - 14999
// api-ldap-schema-converter 15000 - 15999
diff --git a/i18n/src/main/resources/org/apache/directory/api/i18n/errors.properties b/i18n/src/main/resources/org/apache/directory/api/i18n/errors.properties
index b780933..f540e57 100644
--- a/i18n/src/main/resources/org/apache/directory/api/i18n/errors.properties
+++ b/i18n/src/main/resources/org/apache/directory/api/i18n/errors.properties
@@ -770,6 +770,34 @@ ERR_13777_COLLECTIVE_NOT_MULTI_VALUED=The Collective Attribute ({0}) cannot be s
ERR_13778_COLLECTIVE_NOT_ALLOWED_IN_MUST=The Collective Attribute ({0}) cannot be added in the MUST list of the {1} ObjectClass
ERR_13779_COLLECTIVE_NOT_ALLOWED_IN_MAY=The Collective Attribute ({0}) cannot be added in the MAY list of the {1} ObjectClass
+ERR_13780_AT_DESCRIPTION_HAS_ELEMENT_TWICE=Can''t have a {0} twice in an AttributeTypeDescription (line {1}, col {2})
+ERR_13781_OC_DESCRIPTION_HAS_ELEMENT_TWICE=Can''t have a {0} twice in an ObjectClassDescription (line {1}, col {2})
+ERR_13782_END_OF_FILE=End of file met (line {0}, col {1})
+ERR_13783_SPACE_EXPECTED=Space expected (line {0}, col {1})
+ERR_13784_BAD_OID_TWO_ZEROES=Bad OID, we have a number that starts with ''0'' (line {0}, col {1})
+ERR_13785_BAD_OID_CONSECUTIVE_DOTS=Bad OID, we have dots at the wrong place (line {0}, col {1})
+ERR_13786_BAD_OID_DOT_AT_THE_END=Bad OID, we can''t have a dot at the end of the OID (line {0}, col {1})
+ERR_13787_OID_EXPECTED=An oid is expected (line {0}, col {1})
+ERR_13788_LEAD_KEY_CHAR_EXPECTED=leadkeychar expected (line {0}, col {1})
+ERR_13789_SIMPLE_QUOTE_EXPECTED_AT_START=Simple quote expected for a descr (line {0}, col {1})
+ERR_13790_NOT_A_KEYSTRING=Not a keystring (line {0}, col {1})
+ERR_13791_KEYCHAR_EXPECTED=keychar expected, got {0} (line {1}, col {2})
+ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END=Simple quote expected at the end of a descr (line {0}, col {1})
+ERR_13793_NO_CLOSING_PAREN=No closing '')'' (line {0}, col {1})
+ERR_13794_MORE_OIDS_EXPECTED=more OIDs expected (line {0}, col {1})
+ERR_13795_OPENED_BRACKET_NOT_CLOSED=Opened ''{'' hasn't been closed (line {0}, col {1})
+ERR_13796_USAGE_EXPECTED=Expected an USAGE (line {0}, col {1})
+ERR_13797_USAGE_UNKNOWN=The USAGE value is unknown (line {0}, col {1})
+ERR_13798_AT_DESCRIPTION_INVALID=Error, AttributeType definition invalid (line {0}, col {1})
+ERR_13799_SYNTAX_OR_SUP_REQUIRED=One of SYNTAX or SUP is required (line {0}, col {1})
+ERR_13800_COLLECTIVE_REQUIRES_USER_APPLICATION=COLLECTIVE requires USAGE userApplications (line {0}, col {1})
+ERR_13801_NO_USER_MOD_REQUIRE_OPERATIONAL=NO-USER-MODIFICATION requires an operational USAGE (line {0}, col {1})
+ERR_13802_EXTENSION_SHOULD_START_WITH_X=Error, extension should start with ''X-'' (line {0}, col {1})
+ERR_13803_OC_DESCRIPTION_INVALID=Error, ObjectClass definition invalid (line {0}, col {1})
+ERR_13804_OBJECT_IDENTIFIER_HAS_NO_OID=The ObjectIdentifier does not have any OID (line {0}, col {1})
+ERR_13805_OBJECT_IDENTIFIER_INVALID_OID=The ObjectIdentifier has an invalid OID (line {0}, col {1})
+ERR_13806_UNEXPECTED_ELEMENT_READ=Unexpected element read: {0} (line {1}, col {2})
+
# api-ldap-net-mina 14000-14999
# api-ldap-schema-converter 15000-15999
diff --git a/ldap/model/src/checkstyle/suppressions.xml b/ldap/model/src/checkstyle/suppressions.xml
index e61d8b7..a638848 100644
--- a/ldap/model/src/checkstyle/suppressions.xml
+++ b/ldap/model/src/checkstyle/suppressions.xml
@@ -55,6 +55,7 @@
<suppress files="org.apache.directory.api.ldap.model.entry.DefaultAttribute" checks="FileLength" />
<suppress files="org.apache.directory.api.ldap.model.entry.DefaultEntry" checks="FileLength" />
<suppress files="org.apache.directory.api.ldap.model.ldif.LdifReader" checks="FileLength" />
+ <suppress files="org.apache.directory.api.ldap.model.schema.parsers.FastOpenLdapSchemaParser" checks="FileLength" />
<!-- Setter return super type, which is not recognized by Checkstyle -->
<suppress files="org.apache.directory.api.ldap.model.message" checks="HiddenField" />
diff --git a/ldap/model/src/main/java/org/apache/directory/api/ldap/model/schema/parsers/FastOpenLdapSchemaParser.java b/ldap/model/src/main/java/org/apache/directory/api/ldap/model/schema/parsers/FastOpenLdapSchemaParser.java
new file mode 100644
index 0000000..12ae6cc
--- /dev/null
+++ b/ldap/model/src/main/java/org/apache/directory/api/ldap/model/schema/parsers/FastOpenLdapSchemaParser.java
@@ -0,0 +1,2266 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.directory.api.ldap.model.schema.parsers;
+
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.directory.api.i18n.I18n;
+import org.apache.directory.api.ldap.model.exception.LdapSchemaException;
+import org.apache.directory.api.ldap.model.ldif.LdapLdifException;
+import org.apache.directory.api.ldap.model.schema.AttributeType;
+import org.apache.directory.api.ldap.model.schema.MutableAttributeType;
+import org.apache.directory.api.ldap.model.schema.MutableObjectClass;
+import org.apache.directory.api.ldap.model.schema.ObjectClass;
+import org.apache.directory.api.ldap.model.schema.ObjectClassTypeEnum;
+import org.apache.directory.api.ldap.model.schema.SchemaObject;
+import org.apache.directory.api.ldap.model.schema.UsageEnum;
+import org.apache.directory.api.ldap.model.schema.syntaxCheckers.OpenLdapObjectIdentifierMacro;
+import org.apache.directory.api.util.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A reusable wrapper for hand parser OpenLDAP schema.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class FastOpenLdapSchemaParser
+{
+ /** The LoggerFactory used by this class */
+ protected static final Logger LOG = LoggerFactory.getLogger( FastOpenLdapSchemaParser.class );
+
+ /** the monitor to use for this parser */
+ protected ParserMonitor monitor = new ParserMonitorAdapter();
+
+ /** A flag used to tell the parser if it should be strict or not */
+ private boolean isQuirksModeEnabled = false;
+
+ /** the number of the current line being parsed by the reader */
+ protected int lineNumber;
+
+ /** The list of parsed schema descriptions */
+ private List<Object> schemaDescriptions = new ArrayList<>();
+
+ /** The list of attribute type, initialized by splitParsedSchemaDescriptions() */
+ private List<MutableAttributeType> attributeTypes;
+
+ /** The list of object classes, initialized by splitParsedSchemaDescriptions()*/
+ private List<ObjectClass> objectClasses;
+
+ /** The map of object identifier macros, initialized by splitParsedSchemaDescriptions()*/
+ private Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros = new HashMap<>();
+
+ /** Flag whether object identifier macros should be resolved. */
+ private boolean isResolveObjectIdentifierMacros;
+
+ private static final boolean QUOTED = true;
+ private static final boolean UN_QUOTED = false;
+
+
+ private class Extension
+ {
+ /** The extension key */
+ String key;
+
+ /** The extension values */
+ List<String> values = new ArrayList<>();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append( key );
+
+ if ( values.size() > 1 )
+ {
+ boolean isFirst = true;
+ sb.append( "( " );
+
+ for ( String value : values )
+ {
+ if ( isFirst )
+ {
+ isFirst = false;
+ }
+ else
+ {
+ sb.append( ' ' );
+ }
+
+ sb.append( '\'' ) .append( value ).append( '\'' );
+ }
+
+ sb.append( " )" );
+ }
+ else
+ {
+ sb.append( ' ' ).append( '\'' ) .append( values.get( 0 ) ).append( '\'' );
+ }
+
+ return sb.toString();
+ }
+ }
+
+ private class NoidLen
+ {
+ /** The syntax OID */
+ String noid;
+
+ /** The syntax length */
+ long len = 0;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ if ( len > 0 )
+ {
+ return noid + '{' + len + '}';
+ }
+ else
+ {
+ return noid;
+ }
+ }
+ }
+
+
+ private class PosSchema
+ {
+ /** The line number in the file */
+ int lineNumber;
+
+ /** The position in the current line */
+ int start;
+
+ /** The line being processed */
+ String line;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ if ( line == null )
+ {
+ return "null";
+ }
+ else if ( line.length() < start )
+ {
+ return "EOL";
+ }
+ else
+ {
+ return line.substring( start );
+ }
+ }
+ }
+
+
+ /**
+ * The list of AttributeTypeDescription elements that can be seen
+ */
+ private enum AttributeTypeElements
+ {
+ NAME(1),
+ DESC(2),
+ OBSOLETE(4),
+ SUP(8),
+ EQUALITY(16),
+ ORDERING(32),
+ SUBSTR(64),
+ SYNTAX(128),
+ SINGLE_VALUE(256),
+ COLLECTIVE(512),
+ NO_USER_MODIFICATION(1024),
+ USAGE(2048);
+
+ private int value;
+
+ AttributeTypeElements( int value )
+ {
+ this.value = value;
+ }
+ }
+
+
+ /**
+ * The list of ObjectClassDescription elements that can be seen
+ */
+ private enum ObjectClassElements
+ {
+ NAME(1),
+ DESC(2),
+ OBSOLETE(4),
+ SUP(8),
+ MUST(16),
+ MAY(32),
+ ABSTRACT(64),
+ STRUCTURAL(64),
+ AUXILIARY(64);
+
+ private int value;
+
+ ObjectClassElements( int value )
+ {
+ this.value = value;
+ }
+ }
+
+ /**
+ * Creates a reusable instance of an OpenLdapSchemaParser.
+ *
+ * @throws IOException if the pipe cannot be formed
+ */
+ public FastOpenLdapSchemaParser() throws IOException
+ {
+ isResolveObjectIdentifierMacros = true;
+ isQuirksModeEnabled = true;
+ }
+
+
+ /**
+ * Reset the parser
+ */
+ public void clear()
+ {
+ if ( attributeTypes != null )
+ {
+ attributeTypes.clear();
+ }
+
+ if ( objectClasses != null )
+ {
+ objectClasses.clear();
+ }
+
+ if ( schemaDescriptions != null )
+ {
+ schemaDescriptions.clear();
+ }
+
+ if ( objectIdentifierMacros != null )
+ {
+ objectIdentifierMacros.clear();
+ }
+ }
+
+
+ /**
+ * Gets the attribute types.
+ *
+ * @return the attribute types
+ */
+ public List<MutableAttributeType> getAttributeTypes()
+ {
+ return attributeTypes;
+ }
+
+
+ /**
+ * Gets the object class types.
+ *
+ * @return the object class types
+ */
+ public List<ObjectClass> getObjectClassTypes()
+ {
+ return objectClasses;
+ }
+
+
+ /**
+ * Gets the object identifier macros.
+ *
+ * @return the object identifier macros
+ */
+ public Map<String, OpenLdapObjectIdentifierMacro> getObjectIdentifierMacros()
+ {
+ return objectIdentifierMacros;
+ }
+
+
+ /**
+ * Splits parsed schema descriptions and resolved
+ * object identifier macros.
+ *
+ * @throws ParseException the parse exception
+ */
+ private void afterParse() throws ParseException
+ {
+ objectClasses = new ArrayList<>();
+ attributeTypes = new ArrayList<>();
+
+ // split parsed schema descriptions
+ for ( Object obj : schemaDescriptions )
+ {
+ if ( obj instanceof OpenLdapObjectIdentifierMacro )
+ {
+ OpenLdapObjectIdentifierMacro oid = ( OpenLdapObjectIdentifierMacro ) obj;
+ objectIdentifierMacros.put( oid.getName(), oid );
+ }
+ else if ( obj instanceof AttributeType )
+ {
+ MutableAttributeType attributeType = ( MutableAttributeType ) obj;
+
+ attributeTypes.add( attributeType );
+ }
+ else if ( obj instanceof ObjectClass )
+ {
+ ObjectClass objectClass = ( ObjectClass ) obj;
+
+ objectClasses.add( objectClass );
+ }
+ }
+
+ if ( isResolveObjectIdentifierMacros() )
+ {
+ // resolve object identifier macros
+ for ( OpenLdapObjectIdentifierMacro oid : objectIdentifierMacros.values() )
+ {
+ resolveObjectIdentifierMacro( oid );
+ }
+
+ // apply object identifier macros to object classes
+ for ( ObjectClass objectClass : objectClasses )
+ {
+ objectClass.setOid( getResolveOid( objectClass.getOid() ) );
+ }
+
+ // apply object identifier macros to attribute types
+ for ( MutableAttributeType attributeType : attributeTypes )
+ {
+ attributeType.setOid( getResolveOid( attributeType.getOid() ) );
+ attributeType.setSyntaxOid( getResolveOid( attributeType.getSyntaxOid() ) );
+ }
+
+ }
+ }
+
+
+ private String getResolveOid( String oid )
+ {
+ if ( oid != null && oid.indexOf( ':' ) != -1 )
+ {
+ // resolve OID
+ String[] nameAndSuffix = oid.split( ":" );
+
+ if ( objectIdentifierMacros.containsKey( nameAndSuffix[0] ) )
+ {
+ OpenLdapObjectIdentifierMacro macro = objectIdentifierMacros.get( nameAndSuffix[0] );
+
+ return macro.getResolvedOid() + "." + nameAndSuffix[1];
+ }
+ }
+
+ return oid;
+ }
+
+
+ private void resolveObjectIdentifierMacro( OpenLdapObjectIdentifierMacro macro ) throws ParseException
+ {
+ String rawOidOrNameSuffix = macro.getRawOidOrNameSuffix();
+
+ if ( macro.isResolved() )
+ {
+ // finished
+ return;
+ }
+ else if ( rawOidOrNameSuffix.indexOf( ':' ) != -1 )
+ {
+ // resolve OID
+ String[] nameAndSuffix = rawOidOrNameSuffix.split( ":" );
+
+ if ( objectIdentifierMacros.containsKey( nameAndSuffix[0] ) )
+ {
+ OpenLdapObjectIdentifierMacro parentMacro = objectIdentifierMacros.get( nameAndSuffix[0] );
+ resolveObjectIdentifierMacro( parentMacro );
+ macro.setResolvedOid( parentMacro.getResolvedOid() + "." + nameAndSuffix[1] );
+ }
+ else
+ {
+ throw new ParseException( I18n.err( I18n.ERR_13726_NO_OBJECT_IDENTIFIER_MACRO, nameAndSuffix[0] ), 0 );
+ }
+
+ }
+ else
+ {
+ // no :suffix,
+ if ( objectIdentifierMacros.containsKey( rawOidOrNameSuffix ) )
+ {
+ OpenLdapObjectIdentifierMacro parentMacro = objectIdentifierMacros.get( rawOidOrNameSuffix );
+ resolveObjectIdentifierMacro( parentMacro );
+ macro.setResolvedOid( parentMacro.getResolvedOid() );
+ }
+ else
+ {
+ macro.setResolvedOid( rawOidOrNameSuffix );
+ }
+ }
+ }
+
+
+ /**
+ * Parses an OpenLDAP schemaObject element/object.
+ *
+ * @param schemaObject the String image of a complete schema object
+ * @return the schema object
+ * @throws ParseException If the schemaObject can't be parsed
+ */
+ public SchemaObject parse( String schemaObject ) throws ParseException
+ {
+ if ( ( schemaObject == null ) || Strings.isEmpty( schemaObject.trim() ) )
+ {
+ throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
+ }
+
+ try ( Reader reader = new BufferedReader( new StringReader( schemaObject ) ) )
+ {
+ parse( reader );
+ afterParse();
+ }
+ catch ( IOException | LdapSchemaException e )
+ {
+ throw new ParseException( e.getMessage(), 0 );
+ }
+
+ if ( !schemaDescriptions.isEmpty() )
+ {
+ for ( Object obj : schemaDescriptions )
+ {
+ if ( obj instanceof SchemaObject )
+ {
+ return ( SchemaObject ) obj;
+ }
+ }
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Parses a stream of OpenLDAP schemaObject elements/objects. Default charset is used.
+ *
+ * @param schemaIn a stream of schema objects
+ * @throws Exception
+ */
+ public void parse( InputStream schemaIn ) throws Exception
+ {
+ try ( InputStreamReader in = new InputStreamReader( schemaIn, Charset.defaultCharset() ) )
+ {
+ try ( Reader reader = new BufferedReader( in ) )
+ {
+ parse( reader );
+ afterParse();
+ }
+ }
+ }
+
+
+ private void skipWhites( Reader reader, PosSchema pos, boolean mandatory ) throws IOException, LdapSchemaException
+ {
+ boolean hasSpace = false;
+
+ while ( true )
+ {
+ if ( isEmpty( pos ) )
+ {
+ getLine( reader, pos );
+
+ if ( pos.line == null )
+ {
+ return;
+ }
+
+ hasSpace = true;
+ continue;
+ }
+
+ if ( pos.line == null )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13782_END_OF_FILE, pos.lineNumber, pos.start ) );
+ }
+
+ while ( Character.isWhitespace( pos.line.charAt( pos.start ) ) )
+ {
+ hasSpace = true;
+ pos.start++;
+
+ if ( isEmpty( pos ) )
+ {
+ getLine( reader, pos );
+
+ if ( pos.line == null )
+ {
+ return;
+ }
+
+ continue;
+ }
+ }
+
+ if ( pos.line.charAt( pos.start ) == '#' )
+ {
+ getLine( reader, pos );
+
+ if ( pos.line == null )
+ {
+ return;
+ }
+
+ hasSpace = true;
+ continue;
+ }
+ else
+ {
+ if ( mandatory && !hasSpace )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13783_SPACE_EXPECTED, pos.lineNumber, pos.start ) );
+ }
+ else
+ {
+ return;
+ }
+ }
+ }
+ }
+
+
+ private boolean isComment( PosSchema pos )
+ {
+ if ( isEmpty( pos ) )
+ {
+ return true;
+ }
+
+ return pos.line.charAt( pos.start ) == '#';
+ }
+
+
+ private boolean isEmpty( PosSchema pos )
+ {
+ return ( pos.line == null ) || ( pos.start >= pos.line.length() );
+ }
+
+
+ private boolean startsWith( PosSchema pos, String text )
+ {
+ if ( ( pos.line == null ) || ( pos.line.length() - pos.start < text.length() ) )
+ {
+ return false;
+ }
+
+ return text.equalsIgnoreCase( pos.line.substring( pos.start, pos.start + text.length() ) );
+ }
+
+
+ private boolean startsWith( Reader reader, PosSchema pos, char c ) throws IOException, LdapSchemaException
+ {
+ return startsWith( reader, pos, c, UN_QUOTED );
+ }
+
+
+ private boolean startsWith( Reader reader, PosSchema pos, char c, boolean quoted ) throws IOException, LdapSchemaException
+ {
+ if ( ( pos.line == null ) || ( pos.line.length() - pos.start < 1 ) )
+ {
+ return false;
+ }
+
+ if ( quoted )
+ {
+ // Don't read a new line when we are within quotes
+ return pos.line.charAt( pos.start ) == c;
+ }
+
+ while ( isEmpty( pos ) || ( isComment( pos ) ) )
+ {
+ getLine( reader, pos );
+
+ if ( pos.line == null )
+ {
+ return false;
+ }
+
+ skipWhites( reader, pos, false );
+
+ if ( isComment( pos ) )
+ {
+ continue;
+ }
+ }
+
+ return pos.line.charAt( pos.start ) == c;
+ }
+
+
+ private boolean isAlpha( PosSchema pos )
+ {
+ if ( ( pos.line == null ) || ( pos.line.length() - pos.start < 1 ) )
+ {
+ return false;
+ }
+
+ return Character.isAlphabetic( pos.line.charAt( pos.start ) );
+ }
+
+
+ private boolean isDigit( PosSchema pos )
+ {
+ if ( ( pos.line == null ) || ( pos.line.length() - pos.start < 1 ) )
+ {
+ return false;
+ }
+
+ return Character.isDigit( pos.line.charAt( pos.start ) );
+ }
+
+
+ private void getLine( Reader reader, PosSchema pos ) throws IOException
+ {
+ pos.line = ( ( BufferedReader ) reader ).readLine();
+ pos.start = 0;
+
+ if ( pos.line != null )
+ {
+ pos.lineNumber++;
+ }
+ }
+
+
+ /**
+ * numericoid ::= number ( DOT number )+ |
+ * number ::= DIGIT | LDIGIT DIGIT+
+ * DIGIT ::= %x30 | LDIGIT ; "0"-"9"
+ * LDIGIT ::= %x31-39 ; "1"-"9"
+ * DOT ::= %x2E ; period (".")
+ */
+ private String getNumericOid( Reader reader, PosSchema pos ) throws IOException, LdapSchemaException
+ {
+ boolean isDot = true;
+ int start = pos.start;
+ int numberStart = start;
+ boolean firstIsZero = false;
+ boolean isFirstDigit = true;
+
+ while ( true )
+ {
+ if ( isDigit( pos ) )
+ {
+ if ( firstIsZero )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13784_BAD_OID_TWO_ZEROES, pos.lineNumber, pos.start ) );
+ }
+
+ if ( ( pos.line.charAt( pos.start ) == '0' ) && isFirstDigit )
+ {
+ firstIsZero = true;
+ }
+
+ isDot = false;
+ pos.start++;
+ isFirstDigit = false;
+ }
+ else if ( startsWith( reader, pos, '.' ) )
+ {
+ if ( isDot )
+ {
+ // We can't have two consecutive dots or a dot at the beginning
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13785_BAD_OID_CONSECUTIVE_DOTS, pos.lineNumber, pos.start ) );
+ }
+
+ firstIsZero = false;
+ isFirstDigit = true;
+ pos.start++;
+ isDot = true;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if ( isDot )
+ {
+ // We can't have two consecutive dots or a dot at the beginning
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13786_BAD_OID_DOT_AT_THE_END, pos.lineNumber, pos.start ) );
+ }
+
+ return pos.line.substring( start, pos.start );
+ }
+
+
+ /**
+ * In normal mode :
+ * <pre>
+ * oid ::= descr | numericoid
+ * descr ::= keystring
+ * keystring ::= leadkeychar keychar*
+ * leadkeychar ::= ALPHA
+ * keychar ::= ALPHA | DIGIT | HYPHEN
+ * numericoid ::= number ( DOT number )+ |
+ * number ::= DIGIT | LDIGIT DIGIT+
+ * ALPHA ::= %x41-5A | %x61-7A ; "A"-"Z" / "a"-"z"
+ * DIGIT ::= %x30 | LDIGIT ; "0"-"9"
+ * LDIGIT ::= %x31-39 ; "1"-"9"
+ * DOT ::= %x2E ; period (".")
+ * HYPHEN ::= %x2D ; hyphen ("-")
+ * </pre>
+ *
+ * In quirks mode :
+ * <pre>
+ * oid ::= descr | numericoid
+ * descr ::= descrQ (COLON numericoid)
+ * descrQ ::= keystringQ
+ * keystringQ ::= LkeycharQ keycharQ*
+ * LkeycharQ ::= ALPHA | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP
+ * keycharQ ::= ALPHA | DIGIT | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP
+ * numericoid ::= number ( DOT number )+
+ * number ::= DIGIT | LDIGIT DIGIT+
+ * ALPHA ::= %x41-5A | %x61-7A ; "A"-"Z" / "a"-"z"
+ * DIGIT ::= %x30 | LDIGIT ; "0"-"9"
+ * LDIGIT ::= %x31-39 ; "1"-"9"
+ * HYPHEN ::= %x2D ; hyphen ("-")
+ * UNDERSCORE ::= %x5F ; underscore ("_")
+ * DOT ::= %x2E ; period (".")
+ * COLON ::= %x3A ; colon (":")
+ * SEMI_COLON ::= %x3B ; semi-colon(";")
+ * SHARP ::= %x23 ; octothorpe (or sharp sign) ("#")
+ * </pre>
+ */
+ private String getOidAndMacro( Reader reader, PosSchema pos ) throws IOException, LdapSchemaException
+ {
+ if ( isAlpha( pos ) )
+ {
+ // A descr (likely)
+ if ( isQuirksModeEnabled )
+ {
+ // This is a OID name
+ int start = pos.start;
+ char c = pos.line.charAt( pos.start );
+
+ while ( Character.isDigit( c ) || Character.isAlphabetic( c ) || ( c == '-' ) || ( c == '_' )
+ || ( c == ';' ) || ( c == '.' ) || ( c == '#' ) )
+ {
+ pos.start++;
+
+ if ( isEmpty( pos ) )
+ {
+ break;
+ }
+ }
+
+ String oidName = pos.line.substring( start, pos.start );
+
+ // We may have a ':' followed by an OID
+ if ( startsWith( reader, pos, ':' ) )
+ {
+ pos.start++;
+
+ String oid = getNumericOid( reader, pos );
+
+ return objectIdentifierMacros.get( oidName ).getRawOidOrNameSuffix() + '.' + oid;
+ }
+ else
+ {
+ // Ok, we may just have an oidName
+ OpenLdapObjectIdentifierMacro macro = objectIdentifierMacros.get( oidName );
+
+ if ( macro == null )
+ {
+ return oidName;
+ }
+ else
+ {
+ return macro.getRawOidOrNameSuffix();
+ }
+ }
+ }
+ else
+ {
+ // A simple descr
+ return getDescr( reader, pos );
+ }
+ }
+ else if ( isDigit( pos ) )
+ {
+ // This is a numeric oid
+ return getNumericOid( reader, pos );
+ }
+ else
+ {
+ // This is an error
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
+ }
+ }
+
+
+ /**
+ * In normal mode :
+ * <pre>
+ * oid ::= descr | numericoid
+ * descr ::= keystring
+ * keystring ::= leadkeychar keychar*
+ * leadkeychar ::= ALPHA
+ * keychar ::= ALPHA | DIGIT | HYPHEN
+ * numericoid ::= number ( DOT number )+ |
+ * number ::= DIGIT | LDIGIT DIGIT+
+ * ALPHA ::= %x41-5A | %x61-7A ; "A"-"Z" / "a"-"z"
+ * DIGIT ::= %x30 | LDIGIT ; "0"-"9"
+ * LDIGIT ::= %x31-39 ; "1"-"9"
+ * DOT ::= %x2E ; period (".")
+ * HYPHEN ::= %x2D ; hyphen ("-")
+ * </pre>
+ *
+ * In quirks mode :
+ * <pre>
+ * oid ::= descr | numericoid
+ * descr ::= descrQ (COLON numericoid)
+ * descrQ ::= keystringQ
+ * keystringQ ::= LkeycharQ keycharQ*
+ * LkeycharQ ::= ALPHA | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP
+ * keycharQ ::= ALPHA | DIGIT | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP
+ * numericoid ::= number ( DOT number )+
+ * number ::= DIGIT | LDIGIT DIGIT+
+ * ALPHA ::= %x41-5A | %x61-7A ; "A"-"Z" / "a"-"z"
+ * DIGIT ::= %x30 | LDIGIT ; "0"-"9"
+ * LDIGIT ::= %x31-39 ; "1"-"9"
+ * HYPHEN ::= %x2D ; hyphen ("-")
+ * UNDERSCORE ::= %x5F ; underscore ("_")
+ * DOT ::= %x2E ; period (".")
+ * COLON ::= %x3A ; colon (":")
+ * SEMI_COLON ::= %x3B ; semi-colon(";")
+ * SHARP ::= %x23 ; octothorpe (or sharp sign) ("#")
+ * </pre>
+ */
+ private String getOid( Reader reader, PosSchema pos ) throws IOException, LdapSchemaException
+ {
+ if ( isAlpha( pos ) )
+ {
+ // A descr (likely)
+ if ( isQuirksModeEnabled )
+ {
+ // This is a OID name
+ int start = pos.start;
+ char c = pos.line.charAt( pos.start );
+
+ while ( Character.isAlphabetic( c ) || Character.isDigit( c ) || ( c == '-' )
+ || ( c == '_' ) || ( c == ';' ) || ( c == '.' ) || ( c == ':' ) || ( c == '#' ) )
+ {
+ pos.start++;
+
+ if ( isEmpty( pos ) )
+ {
+ break;
+ }
+
+ c = pos.line.charAt( pos.start );
+ }
+
+ return pos.line.substring( start, pos.start );
+ }
+ else
+ {
+ // A simple descr
+ return getDescr( reader, pos );
+ }
+ }
+ else if ( isDigit( pos ) )
+ {
+ // This is a numeric oid
+ return getNumericOid( reader, pos );
+ }
+ else
+ {
+ // This is an error
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
+ }
+ }
+
+
+ /**
+ * In normal mode :
+ *
+ * <pre>
+ * descr ::= keystring
+ * keystring ::= leadkeychar keychar*
+ * leadkeychar ::= ALPHA
+ * keychar ::= ALPHA | DIGIT | HYPHEN
+ * numericoid ::= number ( DOT number )+ |
+ * number ::= DIGIT | LDIGIT DIGIT+
+ * ALPHA ::= %x41-5A | %x61-7A ; "A"-"Z" / "a"-"z"
+ * DIGIT ::= %x30 | LDIGIT ; "0"-"9"
+ * LDIGIT ::= %x31-39 ; "1"-"9"
+ * DOT ::= %x2E ; period (".")
+ * HYPHEN ::= %x2D ; hyphen ("-")
+ * </pre>
+ *
+ * In quirksMode :
+ *
+ * <pre>
+ * descr ::= descrQ (COLON numericoid)
+ * descrQ ::= keystringQ
+ * keystringQ ::= LkeycharQ keycharQ*
+ * LkeycharQ ::= ALPHA | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP
+ * keycharQ ::= ALPHA | DIGIT | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP
+ * numericoid ::= number ( DOT number )+
+ * number ::= DIGIT | LDIGIT DIGIT+
+ * ALPHA ::= %x41-5A | %x61-7A ; "A"-"Z" / "a"-"z"
+ * DIGIT ::= %x30 | LDIGIT ; "0"-"9"
+ * LDIGIT ::= %x31-39 ; "1"-"9"
+ * HYPHEN ::= %x2D ; hyphen ("-")
+ * UNDERSCORE ::= %x5F ; underscore ("_")
+ * DOT ::= %x2E ; period (".")
+ * COLON ::= %x3A ; colon (":")
+ * SEMI_COLON ::= %x3B ; semi-colon(";")
+ * SHARP ::= %x23 ; octothorpe (or sharp sign) ("#")
+ * </pre
+ * @throws IOException
+ */
+ private String getDescr( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
+ {
+ if ( isQuirksModeEnabled )
+ {
+ int start = pos.start;
+ boolean isFirst = true;
+
+ while ( !isEmpty( pos ) )
+ {
+ if ( isFirst )
+ {
+ isFirst = false;
+
+ char c = pos.line.charAt( pos.start );
+
+ if ( Character.isAlphabetic( c ) || ( c == '-' ) || ( c == '_' )
+ || ( c == ';' ) || ( c == '.' ) || ( c == ':' ) || ( c == '#' ) )
+ {
+ // leadkeycharQ
+ pos.start++;
+ }
+ else
+ {
+ // Error, we are expecting a leadKeychar
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13788_LEAD_KEY_CHAR_EXPECTED,
+ pos.lineNumber, pos.start ) );
+ }
+ }
+ else
+ {
+ char c = pos.line.charAt( pos.start );
+
+ if ( Character.isAlphabetic( c ) || Character.isDigit( c ) || ( c == '-' )
+ || ( c == '_' ) || ( c == ';' ) || ( c == '.' ) || ( c == ':' ) || ( c == '#' ) )
+ {
+ pos.start++;
+ }
+ else
+ {
+ // We are done
+ return pos.line.substring( start, pos.start );
+ }
+ }
+ }
+
+ return pos.line.substring( start, pos.start );
+ }
+ else
+ {
+ int start = pos.start;
+ boolean isFirst = true;
+
+ while ( !isEmpty( pos ) )
+ {
+ if ( isFirst )
+ {
+ isFirst = false;
+
+ if ( isAlpha( pos ) )
+ {
+ // leadkeychar
+ pos.start++;
+ }
+ else
+ {
+ // Error, we are expecting a leadKeychar
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13788_LEAD_KEY_CHAR_EXPECTED,
+ pos.lineNumber, pos.start ) );
+ }
+ }
+ else
+ {
+ char c = pos.line.charAt( pos.start );
+
+ if ( Character.isAlphabetic( c ) || Character.isDigit( c ) || ( c == '-' ) )
+ {
+ pos.start++;
+ }
+ else
+ {
+ // We are done
+ return pos.line.substring( start, pos.start );
+ }
+ }
+ }
+
+ return pos.line.substring( start, pos.start );
+ }
+ }
+
+
+ private String getMacro( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
+ {
+ if ( isQuirksModeEnabled )
+ {
+ int start = pos.start;
+ boolean isFirst = true;
+
+ while ( !isEmpty( pos ) )
+ {
+ if ( isFirst )
+ {
+ isFirst = false;
+
+ char c = pos.line.charAt( pos.start );
+
+ if ( Character.isAlphabetic( c ) || ( c == '-' ) || ( c == '_' )
+ || ( c == ';' ) || ( c == '.' ) || ( c == '#' ) )
+ {
+ // leadkeycharQ
+ pos.start++;
+ }
+ else
+ {
+ // Error, we are expecting a leadKeychar
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13788_LEAD_KEY_CHAR_EXPECTED,
+ pos.lineNumber, pos.start ) );
+ }
+ }
+ else
+ {
+ char c = pos.line.charAt( pos.start );
+
+ if ( Character.isAlphabetic( c ) || Character.isDigit( c ) || ( c == '-' )
+ || ( c == '_' ) || ( c == ';' ) || ( c == '.' ) || ( c == '#' ) )
+ {
+ pos.start++;
+ }
+ else
+ {
+ // We are done
+ return pos.line.substring( start, pos.start );
+ }
+ }
+ }
+
+ return pos.line.substring( start, pos.start );
+ }
+ else
+ {
+ int start = pos.start;
+ boolean isFirst = true;
+
+ while ( !isEmpty( pos ) )
+ {
+ if ( isFirst )
+ {
+ isFirst = false;
+
+ if ( isAlpha( pos ) )
+ {
+ // leadkeychar
+ pos.start++;
+ }
+ else
+ {
+ // Error, we are expecting a leadKeychar
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13788_LEAD_KEY_CHAR_EXPECTED,
+ pos.lineNumber, pos.start ) );
+ }
+ }
+ else
+ {
+ char c = pos.line.charAt( pos.start );
+
+ if ( Character.isAlphabetic( c ) || Character.isDigit( c ) || ( c == '-' ) )
+ {
+ pos.start++;
+ }
+ else
+ {
+ // We are done
+ return pos.line.substring( start, pos.start );
+ }
+ }
+ }
+
+ return pos.line.substring( start, pos.start );
+ }
+ }
+
+
+
+ /**
+ * <pre>
+ * qdescr ::== SQUOTE descr SQUOTE
+ * descr ::= keystring
+ * keystring ::= leadkeychar *keychar
+ * leadkeychar ::= ALPHA
+ * keychar ::= ALPHA | DIGIT | HYPHEN
+ * </pre>
+ *
+ * In quirksMode :
+ *
+ * <pre>
+ * qdescr ::== SQUOTE descr SQUOTE | descr | SQUOTE numericoid SQUOTE
+ * descr ::= keystring
+ * keystring ::= keychar+
+ * keychar ::= ALPHA | DIGIT | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP
+ * </pre>
+ * @throws IOException
+ */
+ private String getQDescr( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
+ {
+ if ( isQuirksModeEnabled )
+ {
+ if ( startsWith( reader, pos, '\'' ) )
+ {
+ pos.start++;
+ int start = pos.start;
+
+ while ( !startsWith( reader, pos, '\'' ) )
+ {
+ if ( isEmpty( pos ) )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13789_SIMPLE_QUOTE_EXPECTED_AT_START,
+ pos.lineNumber, pos.start ) );
+ }
+
+ char c = pos.line.charAt( pos.start );
+
+ if ( Character.isDigit( c ) || Character.isAlphabetic( c ) || ( c == '-' ) || ( c == '_' )
+ || ( c == ';' ) || ( c == '.' ) || ( c == ':' ) || ( c == '#' ) )
+ {
+ pos.start++;
+ }
+ else if ( c != '\'' )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13790_NOT_A_KEYSTRING, pos.lineNumber, pos.start ) );
+ }
+ }
+
+ pos.start++;
+
+ return pos.line.substring( start, pos.start - 1 );
+ }
+ else
+ {
+ int start = pos.start;
+ while ( !isEmpty( pos ) )
+ {
+ char c = pos.line.charAt( pos.start );
+
+ if ( Character.isDigit( c ) || Character.isAlphabetic( c ) || ( c == '-' ) || ( c == '_' )
+ || ( c == ';' ) || ( c == '.' ) || ( c == ':' ) || ( c == '#' ) )
+ {
+ pos.start++;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return pos.line.substring( start, pos.start );
+ }
+ }
+ else
+ {
+ // The first quote
+ if ( !startsWith( reader, pos, '\'' ) )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13789_SIMPLE_QUOTE_EXPECTED_AT_START,
+ pos.lineNumber, pos.start ) );
+ }
+
+ pos.start++;
+ int start = pos.start;
+ boolean isFirst = true;
+
+ while ( !startsWith( reader, pos, '\'' ) )
+ {
+ if ( isFirst )
+ {
+ isFirst = false;
+
+ if ( isAlpha( pos ) )
+ {
+ // leadkeychar
+ pos.start++;
+ }
+ else
+ {
+ // Error, we are expecting a leadKeychar
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13788_LEAD_KEY_CHAR_EXPECTED,
+ pos.lineNumber, pos.start ) );
+ }
+ }
+ else
+ {
+ char c = pos.line.charAt( pos.start );
+
+ if ( Character.isAlphabetic( c ) || Character.isDigit( c ) || ( c == '-' ) )
+ {
+ pos.start++;
+ }
+ else
+ {
+ // This is an error
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13791_KEYCHAR_EXPECTED, c,
+ pos.lineNumber, pos.start ) );
+ }
+ }
+ }
+
+ if ( startsWith( reader, pos, '\'' ) )
+ {
+ // We are done, move one char forward to eliminate the simple quote
+ pos.start++;
+
+ return pos.line.substring( start, pos.start - 1 );
+ }
+ else
+ {
+ // No closing simple quote, this is an error
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END,
+ pos.lineNumber, pos.start ) );
+ }
+ }
+ }
+
+
+ /**
+ * <pre>
+ * qdstring ::== SQUOTE dstring SQUOTE
+ * dstring ::= ( QS | QQ | QUTF8 )+ ; escaped UTF-8 string
+ * QS ::= ESC %x35 ( %x43 | %x63 ) ; "\5C" | "\5c", escape char
+ * QQ ::= ESC %x32 %x37 ; "\27", simple quote char
+ * QUTF8 ::= QUTF1 | UTFMB
+ * QUTF1 ::= %x00-26 | %x28-5B | %x5D-7F ; All ascii but ' and \
+ * UTFMB ::= UTF2 | UTF3 | UTF4
+ * UTF0 ::= %x80-BF
+ * UTF2 ::= %xC2-DF UTF0
+ * UTF3 ::= %xE0 %xA0-BF UTF0 | %xE1-EC UTF0 UTF0 | %xED %x80-9F UTF0 | %xEE-EF UTF0 UTF0
+ * UTF4 ::= %xF0 %x90-BF UTF0 UTF0 | %xF1-F3 UTF0 UTF0 UTF0 | %xF4 %x80-8F UTF0 UTF0
+ * ESC ::= %x5C ; backslash ("\")
+ * </pre>
+ */
+ private String getQDString( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
+ {
+ // The first quote
+ if ( !startsWith( reader, pos, '\'' ) )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13789_SIMPLE_QUOTE_EXPECTED_AT_START,
+ pos.lineNumber, pos.start ) );
+ }
+
+ pos.start++;
+ int start = pos.start;
+
+ while ( !startsWith( reader, pos, '\'', QUOTED ) )
+ {
+ // At the moment, just swallow anything
+ pos.start++;
+ }
+
+ if ( startsWith( reader, pos, '\'' ) )
+ {
+ // We are done, move one char forward to eliminate the simple quote
+ pos.start++;
+
+ return pos.line.substring( start, pos.start - 1 );
+ }
+ else
+ {
+ // No closing simple quote, this is an error
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END,
+ pos.lineNumber, pos.start ) );
+ }
+ }
+
+
+ /**
+ * qdescrs ::= qdescr | LPAREN WSP qdescrlist WSP RPAREN
+ * qdescrlist ::= [ qdescr *( SP qdescr ) ]
+ * qdescr ::== SQUOTE descr SQUOTE
+ * descr ::= keystring
+ * keystring ::= leadkeychar *keychar
+ * leadkeychar ::= ALPHA
+ * keychar ::= ALPHA / DIGIT / HYPHEN
+ * @throws LdapSchemaException
+ * @throws IOException
+ */
+ private List<String> getQDescrs( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
+ {
+ List<String> qdescrs = new ArrayList<>();
+
+ // It may start with a '('
+ if ( startsWith( reader, pos, '(' ) )
+ {
+ pos.start++;
+
+ // We have more than a name
+ skipWhites( reader, pos, false );
+
+ while ( !startsWith( reader, pos, ')' ) )
+ {
+ qdescrs.add( getQDescr( reader, pos ) );
+
+ if ( startsWith( reader, pos, ')' ) )
+ {
+ break;
+ }
+
+ skipWhites( reader, pos, true );
+ }
+
+ if ( !startsWith( reader, pos, ')' ) )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13793_NO_CLOSING_PAREN,
+ pos.lineNumber, pos.start ) );
+ }
+
+ pos.start++;
+ }
+ else
+ {
+ // Only one name, read it
+ qdescrs.add( getQDescr( reader, pos ) );
+ }
+
+ return qdescrs;
+ }
+
+
+ /**
+ * <pre>
+ * qdstrings ::= qdstring | ( LPAREN WSP qdstringlist WSP RPAREN )
+ * qdstringlist ::= qdstring *( SP qdstring )*
+ * qdstring ::= SQUOTE dstring SQUOTE
+ * dstring ::= 1*( QS / QQ / QUTF8 ) ; escaped UTF-8 string
+ * </pre>
+ * @throws LdapSchemaException
+ * @throws IOException
+ */
+ private List<String> getQDStrings( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
+ {
+ List<String> qdStrings = new ArrayList<>();
+
+ // It may start with a '('
+ if ( startsWith( reader, pos, '(' ) )
+ {
+ pos.start++;
+
+ // We have more than a name
+ skipWhites( reader, pos, false );
+
+ while ( !startsWith( reader, pos, ')' ) )
+ {
+ qdStrings.add( getQDString( reader, pos ) );
+
+ if ( startsWith( reader, pos, ')' ) )
+ {
+ break;
+ }
+
+ skipWhites( reader, pos, true );
+ }
+
+ if ( !startsWith( reader, pos, ')' ) )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13793_NO_CLOSING_PAREN,
+ pos.lineNumber, pos.start ) );
+ }
+
+ pos.start++;
+ }
+ else
+ {
+ // Only one name, read it
+ qdStrings.add( getQDString( reader, pos ) );
+ }
+
+ return qdStrings;
+ }
+
+
+
+
+ /**
+ * <pre>
+ * oids ::= oid | ( LPAREN WSP oidlist WSP RPAREN )
+ * oidlist ::= oid *( WSP DOLLAR WSP oid )
+ * </pre>
+ */
+ private List<String> getOids( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
+ {
+ List<String> oids = new ArrayList<>();
+
+ // It may start with a '('
+ if ( startsWith( reader, pos, '(' ) )
+ {
+ pos.start++;
+
+ // We have more than a name
+ skipWhites( reader, pos, false );
+ boolean moreExpected = false;
+
+ while ( !startsWith( reader, pos, ')' ) )
+ {
+ moreExpected = false;
+
+ oids.add( getOid( reader, pos ) );
+
+ if ( startsWith( reader, pos, ')' ) )
+ {
+ break;
+ }
+
+ skipWhites( reader, pos, false );
+
+ if ( startsWith( reader, pos, '$' ) )
+ {
+ pos.start++;
+ moreExpected = true;
+ }
+
+ skipWhites( reader, pos, false );
+ }
+
+ if ( !startsWith( reader, pos, ')' ) )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13793_NO_CLOSING_PAREN,
+ pos.lineNumber, pos.start ) );
+ }
+
+ if ( moreExpected )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13794_MORE_OIDS_EXPECTED,
+ pos.lineNumber, pos.start ) );
+ }
+
+ pos.start++;
+ }
+ else
+ {
+ // Only one name, read it
+ oids.add( getOid( reader, pos ) );
+ }
+
+ return oids;
+ }
+
+
+ /**
+ * noidlen = numericoid [ LCURLY len RCURLY ]
+ */
+ private NoidLen getNoidLen( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
+ {
+ // Get the numericOid
+ String numericOid = getNumericOid( reader, pos );
+ NoidLen noidLen = new NoidLen();
+ noidLen.noid = numericOid;
+
+ // Then the len, if any
+ if ( startsWith( reader, pos, '{' ) )
+ {
+ pos.start++;
+ int start = pos.start;
+
+ while ( isDigit( pos ) )
+ {
+ pos.start++;
+ }
+
+ if ( startsWith( reader, pos, '}' ) )
+ {
+ String lenStr = pos.line.substring( start, pos.start );
+
+ pos.start++;
+
+ if ( Strings.isEmpty( lenStr ) )
+ {
+ noidLen.len = -1;
+ }
+ else
+ {
+ noidLen.len = Integer.parseInt( lenStr );
+ }
+ }
+ else
+ {
+ // The opening curly hasn't been closed
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13795_OPENED_BRACKET_NOT_CLOSED,
+ pos.lineNumber, pos.start ) );
+ }
+ }
+
+ return noidLen;
+ }
+
+
+ private UsageEnum getUsage( Reader reader, PosSchema pos ) throws LdapSchemaException
+ {
+ if ( isEmpty( pos ) )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED,
+ pos.lineNumber, pos.start ) );
+ }
+
+ if ( startsWith( pos, "userApplications" ) )
+ {
+ return UsageEnum.USER_APPLICATIONS;
+ }
+ else if ( startsWith( pos, "directoryOperation" ) )
+ {
+ return UsageEnum.DIRECTORY_OPERATION;
+ }
+ else if ( startsWith( pos, "distributedOperation" ) )
+ {
+ return UsageEnum.DISTRIBUTED_OPERATION;
+ }
+ else if ( startsWith( pos, "dSAOperation" ) )
+ {
+ return UsageEnum.DSA_OPERATION;
+ }
+ else
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13797_USAGE_UNKNOWN,
+ pos.lineNumber, pos.start ) );
+ }
+ }
+
+
+ /**
+ * Production for matching attribute type descriptions. It is fault-tolerant
+ * against element ordering.
+ *
+ * <pre>
+ * AttributeTypeDescription = LPAREN WSP
+ * numericoid ; object identifier
+ * [ SP "NAME" SP qdescrs ] ; short names (descriptors)
+ * [ SP "DESC" SP qdstring ] ; description
+ * [ SP "OBSOLETE" ] ; not active
+ * [ SP "SUP" SP oid ] ; supertype
+ * [ SP "EQUALITY" SP oid ] ; equality matching rule
+ * [ SP "ORDERING" SP oid ] ; ordering matching rule
+ * [ SP "SUBSTR" SP oid ] ; substrings matching rule
+ * [ SP "SYNTAX" SP noidlen ] ; value syntax
+ * [ SP "SINGLE-VALUE" ] ; single-value
+ * [ SP "COLLECTIVE" ] ; collective
+ * [ SP "NO-USER-MODIFICATION" ] ; not user modifiable
+ * [ SP "USAGE" SP usage ] ; usage
+ * extensions WSP RPAREN ; extensions
+ *
+ * usage = "userApplications" / ; user
+ * "directoryOperation" / ; directory operational
+ * "distributedOperation" / ; DSA-shared operational
+ * "dSAOperation" ; DSA-specific operational
+ *
+ * extensions = *( SP xstring SP qdstrings )
+ * xstring = "X" HYPHEN 1*( ALPHA / HYPHEN / USCORE )
+ * </pre>
+ * @throws IOException
+ * @throws LdapSchemaException
+ */
+ private MutableAttributeType parseAttributeType( Reader reader, PosSchema pos ) throws IOException, LdapSchemaException
+ {
+ // Get rid of whites, comments end empty lines
+ skipWhites( reader, pos, false );
+
+ // we must have a '('
+ if ( pos.line.charAt( pos.start ) != '(' )
+ {
+ return null;
+ }
+ else
+ {
+ pos.start++;
+ }
+
+ // Get rid of whites, comments end empty lines
+ skipWhites( reader, pos, false );
+
+ // Now, the OID.
+ String oid = getOidAndMacro( reader, pos );
+
+ MutableAttributeType attributeType = new MutableAttributeType( oid );
+ boolean hasSup = false;
+ boolean hasSyntax = false;
+ int elementsSeen = 0;
+
+ while ( !startsWith( reader, pos, ')' ) )
+ {
+ skipWhites( reader, pos, true );
+
+ if ( startsWith( pos, "NAME" ) )
+ {
+ elementsSeen = check( elementsSeen, AttributeTypeElements.NAME, pos );
+
+ pos.start += "NAME".length();
+
+ skipWhites( reader, pos, true );
+
+ attributeType.setNames( getQDescrs( reader, pos ) );
+ }
+ else if ( startsWith( pos, "DESC" ) )
+ {
+ elementsSeen = check( elementsSeen, AttributeTypeElements.DESC, pos );
+
+ pos.start += "DESC".length();
+
+ skipWhites( reader, pos, true );
+
+ attributeType.setDescription( getQDString( reader, pos ) );
+ }
+ else if ( startsWith( pos, "OBSOLETE" ) )
+ {
+ elementsSeen = check( elementsSeen, AttributeTypeElements.OBSOLETE, pos );
+
+ pos.start += "OBSOLETE".length();
+
+ attributeType.setObsolete( true );
+ }
+ else if ( startsWith( pos, "SUP" ) )
+ {
+ elementsSeen = check( elementsSeen, AttributeTypeElements.SUP, pos );
+
+ pos.start += "SUP".length();
+
+ skipWhites( reader, pos, true );
+
+ String superiorOid = getOid( reader, pos );
+
+ attributeType.setSuperiorOid( superiorOid );
+ hasSup = true;
+ }
+ else if ( startsWith( pos, "EQUALITY" ) )
+ {
+ elementsSeen = check( elementsSeen, AttributeTypeElements.EQUALITY, pos );
+
+ pos.start += "EQUALITY".length();
+
+ skipWhites( reader, pos, true );
+
+ String equalityOid = getOid( reader, pos );
+
+ attributeType.setEqualityOid( equalityOid );
+ }
+ else if ( startsWith( pos, "ORDERING" ) )
+ {
+ elementsSeen = check( elementsSeen, AttributeTypeElements.ORDERING, pos );
+
+ pos.start += "ORDERING".length();
+
+ skipWhites( reader, pos, true );
+
+ String orderingOid = getOid( reader, pos );
+
+ attributeType.setOrderingOid( orderingOid );
+ }
+ else if ( startsWith( pos, "SUBSTR" ) )
+ {
+ elementsSeen = check( elementsSeen, AttributeTypeElements.SUBSTR, pos );
+
+ pos.start += "SUBSTR".length();
+
+ skipWhites( reader, pos, true );
+
+ String substrOid = getOid( reader, pos );
+
+ attributeType.setSubstringOid( substrOid );
+ }
+ else if ( startsWith( pos, "SYNTAX" ) )
+ {
+ elementsSeen = check( elementsSeen, AttributeTypeElements.SYNTAX, pos );
+
+ pos.start += "SYNTAX".length();
+
+ skipWhites( reader, pos, true );
+
+ NoidLen noidLen = getNoidLen( reader, pos );
+
+ attributeType.setSyntaxOid( noidLen.noid );
+ attributeType.setSyntaxLength( noidLen.len );
+ hasSyntax = true;
+ }
+ else if ( startsWith( pos, "SINGLE-VALUE" ) )
+ {
+ elementsSeen = check( elementsSeen, AttributeTypeElements.SINGLE_VALUE, pos );
+
+ pos.start += "SINGLE-VALUE".length();
+
+ attributeType.setSingleValued( true );
+ }
+ else if ( startsWith( pos, "COLLECTIVE" ) )
+ {
+ elementsSeen = check( elementsSeen, AttributeTypeElements.COLLECTIVE, pos );
+
+ pos.start += "COLLECTIVE".length();
+
+ attributeType.setCollective( true );
+ }
+ else if ( startsWith( pos, "NO-USER-MODIFICATION\"" ) )
+ {
+ elementsSeen = check( elementsSeen, AttributeTypeElements.NO_USER_MODIFICATION, pos );
+
+ pos.start += "NO-USER-MODIFICATION\"".length();
+
+ attributeType.setUserModifiable( false );
+ }
+ else if ( startsWith( pos, "USAGE" ) )
+ {
+ elementsSeen = check( elementsSeen, AttributeTypeElements.USAGE, pos );
+
+ pos.start += "USAGE".length();
+
+ skipWhites( reader, pos, true );
+
+ UsageEnum usage = getUsage( reader, pos );
+
+ attributeType.setUsage( usage );
+ }
+ else if ( startsWith( pos, "X-" ) )
+ {
+ Extension extension = getExtension( reader, pos );
+ attributeType.addExtension( extension.key, extension.values );
+ }
+ else if ( startsWith( reader, pos, ')' ) )
+ {
+ pos.start++;
+
+ return attributeType;
+ }
+ else
+ {
+ // This is an error
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13798_AT_DESCRIPTION_INVALID,
+ pos.lineNumber, pos.start ) );
+ }
+ }
+
+ if ( startsWith( reader, pos, ')' ) )
+ {
+ pos.start++;
+
+ // Semantic checks
+ if ( !isQuirksModeEnabled )
+ {
+ if ( !hasSup && !hasSyntax )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13799_SYNTAX_OR_SUP_REQUIRED,
+ pos.lineNumber, pos.start ) );
+ }
+
+ if ( attributeType.isCollective() && ( attributeType.getUsage() != UsageEnum.USER_APPLICATIONS ) )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13800_COLLECTIVE_REQUIRES_USER_APPLICATION,
+ pos.lineNumber, pos.start ) );
+ }
+
+ // NO-USER-MODIFICATION requires an operational USAGE.
+ if ( !attributeType.isUserModifiable() && ( attributeType.getUsage() == UsageEnum.USER_APPLICATIONS ) )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13801_NO_USER_MOD_REQUIRE_OPERATIONAL,
+ pos.lineNumber, pos.start ) );
+ }
+ }
+
+ return attributeType;
+ }
+
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13798_AT_DESCRIPTION_INVALID,
+ pos.lineNumber, pos.start ) );
+ }
+
+
+ private int check( int elementsSeen, AttributeTypeElements element, PosSchema pos ) throws LdapSchemaException
+ {
+ if ( ( elementsSeen & element.value ) != 0 )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13780_AT_DESCRIPTION_HAS_ELEMENT_TWICE, element, pos.lineNumber, pos.start ) );
+ }
+
+ elementsSeen |= element.value;
+
+ return elementsSeen;
+ }
+
+
+ /**
+ * <pre>
+ * extension ::= xstring SP qdstrings
+ * xstring ::= "X" HYPHEN ( ALPHA | HYPHEN | USCORE )+
+ * qdstrings ::= qdstring | ( LPAREN WSP qdstringlist WSP RPAREN )
+ * qdstringlist ::= qdstring *( SP qdstring )*
+ * qdstring ::= SQUOTE dstring SQUOTE
+ * dstring ::= 1*( QS / QQ / QUTF8 ) ; escaped UTF-8 string
+ * </pre>
+ * @throws IOException
+ * @throws LdapSchemaException
+ */
+ private Extension getExtension( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
+ {
+ Extension extension = new Extension();
+
+ // The xstring first
+ extension.key = getXString( reader, pos );
+
+ skipWhites( reader, pos, true );
+
+ extension.values = getQDStrings( reader, pos );
+
+ return extension;
+ }
+
+
+ /**
+ * <pre>
+ * xstring ::= "X" HYPHEN ( ALPHA | HYPHEN | USCORE )+
+ * </pre>
+ */
+ private String getXString( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
+ {
+ int start = pos.start;
+
+ if ( startsWith( pos, "X-" ) )
+ {
+ pos.start += 2;
+
+ // Now parse the remaining string
+ while ( isAlpha( pos ) || startsWith( reader, pos, '-' ) || startsWith( reader, pos, '_' ) )
+ {
+ pos.start++;
+ }
+
+ return pos.line.substring( start, pos.start );
+ }
+ else
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13802_EXTENSION_SHOULD_START_WITH_X,
+ pos.lineNumber, pos.start ) );
+ }
+ }
+
+
+ /**
+ * Production for matching ObjectClass descriptions. It is fault-tolerant
+ * against element ordering.
+ * <pre>
+ * ObjectClassDescription = LPAREN WSP
+ * numericoid ; object identifier
+ * [ SP "NAME" SP qdescrs ] ; short names (descriptors)
+ * [ SP "DESC" SP qdstring ] ; description
+ * [ SP "OBSOLETE" ] ; not active
+ * [ SP "SUP" SP oids ] ; superior object classes
+ * [ SP kind ] ; kind of class
+ * [ SP "MUST" SP oids ] ; attribute types
+ * [ SP "MAY" SP oids ] ; attribute types
+ * extensions WSP RPAREN
+ *
+ * kind = "ABSTRACT" / "STRUCTURAL" / "AUXILIARY"
+ * </pre>
+ */
+ private MutableObjectClass parseObjectClass( Reader reader, PosSchema pos ) throws IOException, LdapSchemaException
+ {
+ // Get rid of whites, comments end empty lines
+ skipWhites( reader, pos, false );
+
+ // we must have a '('
+ if ( pos.line.charAt( pos.start ) != '(' )
+ {
+ return null;
+ }
+ else
+ {
+ pos.start++;
+ }
+
+ // Get rid of whites, comments end empty lines
+ skipWhites( reader, pos, false );
+
+ // Now, the numeric OID
+ String oid = getOid( reader, pos );
+
+ MutableObjectClass objectClass = new MutableObjectClass( oid );
+ int elementsSeen = 0;
+
+ while ( !startsWith( reader, pos, ')' ) )
+ {
+ skipWhites( reader, pos, true );
+
+ if ( startsWith( pos, "NAME" ) )
+ {
+ elementsSeen = check( elementsSeen, ObjectClassElements.NAME, pos );
+
+ pos.start += "NAME".length();
+
+ skipWhites( reader, pos, true );
+
+ List<String> names = getQDescrs( reader, pos );
+ objectClass.setNames( names );
+ }
+ else if ( startsWith( pos, "DESC" ) )
+ {
+ elementsSeen = check( elementsSeen, ObjectClassElements.DESC, pos );
+
+ pos.start += "DESC".length();
+
+ skipWhites( reader, pos, true );
+
+ objectClass.setDescription( getQDString( reader, pos ) );
+ }
+ else if ( startsWith( pos, "OBSOLETE" ) )
+ {
+ elementsSeen = check( elementsSeen, ObjectClassElements.OBSOLETE, pos );
+
+ pos.start += "OBSOLETE".length();
+
+ objectClass.setObsolete( true );
+ }
+ else if ( startsWith( pos, "SUP" ) )
+ {
+ elementsSeen = check( elementsSeen, ObjectClassElements.SUP, pos );
+
+ pos.start += "SUP".length();
+
+ skipWhites( reader, pos, true );
+
+ List<String> superiorOids = getOids( reader, pos );
+
+ objectClass.setSuperiorOids( superiorOids );
+ }
+ else if ( startsWith( pos, "ABSTRACT" ) )
+ {
+ elementsSeen = check( elementsSeen, ObjectClassElements.ABSTRACT, pos );
+
+ pos.start += "ABSTRACT".length();
+
+ objectClass.setType( ObjectClassTypeEnum.ABSTRACT );
+ }
+ else if ( startsWith( pos, "STRUCTURAL" ) )
+ {
+ elementsSeen = check( elementsSeen, ObjectClassElements.STRUCTURAL, pos );
+
+ pos.start += "STRUCTURAL".length();
+
+ objectClass.setType( ObjectClassTypeEnum.STRUCTURAL );
+ }
+ else if ( startsWith( pos, "AUXILIARY" ) )
+ {
+ elementsSeen = check( elementsSeen, ObjectClassElements.AUXILIARY, pos );
+
+ pos.start += "AUXILIARY".length();
+
+ objectClass.setType( ObjectClassTypeEnum.AUXILIARY );
+ }
+ else if ( startsWith( pos, "MUST" ) )
+ {
+ elementsSeen = check( elementsSeen, ObjectClassElements.MUST, pos );
+
+ pos.start += "MUST".length();
+
+ skipWhites( reader, pos, true );
+
+ List<String> mustAttributeTypes = getOids( reader, pos );
+ objectClass.setMustAttributeTypeOids( mustAttributeTypes );
+ }
+ else if ( startsWith( pos, "MAY" ) )
+ {
+ elementsSeen = check( elementsSeen, ObjectClassElements.MAY, pos );
+
+ pos.start += "MAY".length();
+
+ skipWhites( reader, pos, true );
+
+ List<String> mayAttributeTypes = getOids( reader, pos );
+ objectClass.setMayAttributeTypeOids( mayAttributeTypes );
+ }
+ else if ( startsWith( pos, "X-" ) )
+ {
+ Extension extension = getExtension( reader, pos );
+ objectClass.addExtension( extension.key, extension.values );
+ }
+ else if ( startsWith( reader, pos, ')' ) )
+ {
+ pos.start++;
+
+ return objectClass;
+ }
+ else
+ {
+ // This is an error
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13803_OC_DESCRIPTION_INVALID,
+ pos.lineNumber, pos.start ) );
+ }
+ }
+
+ return objectClass;
+ }
+
+
+ private int check( int elementsSeen, ObjectClassElements element, PosSchema pos ) throws LdapSchemaException
+ {
+ if ( ( elementsSeen & element.value ) != 0 )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13781_OC_DESCRIPTION_HAS_ELEMENT_TWICE, element, pos.lineNumber, pos.start ) );
+ }
+
+ elementsSeen |= element.value;
+
+ return elementsSeen;
+ }
+
+
+ /**
+ * Process OpenLDAP macros, like : objectidentifier DUAConfSchemaOID 1.3.6.1.4.1.11.1.3.1.
+ *
+ * <pre>
+ * objectidentifier ::= 'objectidentifier' descr SP+ macroOid
+ * descr ::= ALPHA ( ALPHA | DIGIT | HYPHEN )*
+ * macroOid ::= (descr ':')? oid
+ * </pre>
+ */
+ private void processObjectIdentifier( Reader reader, PosSchema pos ) throws IOException, LdapSchemaException
+ {
+ // Get rid of whites, comments end empty lines
+ skipWhites( reader, pos, false );
+
+ // Now, the name
+ String name = getDescr( reader, pos );
+
+ OpenLdapObjectIdentifierMacro macro = new OpenLdapObjectIdentifierMacro();
+
+ skipWhites( reader, pos, true );
+
+ if ( isEmpty( pos ) )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13804_OBJECT_IDENTIFIER_HAS_NO_OID,
+ pos.lineNumber, pos.start ) );
+ }
+
+ // Get the descr, if any
+ if ( isAlpha( pos ) )
+ {
+ // A macro
+ String descr = getMacro( reader, pos );
+
+ if ( isEmpty( pos ) )
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13804_OBJECT_IDENTIFIER_HAS_NO_OID,
+ pos.lineNumber, pos.start ) );
+ }
+
+ if ( startsWith( reader, pos, ':' ) )
+ {
+ pos.start++;
+
+ // Now, the OID
+ String numericOid = getNumericOid( reader, pos );
+ String realOid = objectIdentifierMacros.get( descr ).getRawOidOrNameSuffix() + '.' + numericOid;
+ macro.setName( name );
+ macro.setRawOidOrNameSuffix( realOid );
+
+ objectIdentifierMacros.put( name, macro );
+
+ return;
+ }
+ }
+ else if ( isDigit( pos ) )
+ {
+ // An oid
+ String numericOid = getNumericOid( reader, pos );
+ macro.setRawOidOrNameSuffix( numericOid );
+ macro.setName( name );
+
+ objectIdentifierMacros.put( name, macro );
+
+ return;
+ }
+ else
+ {
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13805_OBJECT_IDENTIFIER_INVALID_OID,
+ pos.lineNumber, pos.start ) );
+ }
+ }
+
+
+ /**
+ * Reads an entry in a ldif buffer, and returns the resulting lines, without
+ * comments, and unfolded.
+ *
+ * The lines represent *one* entry.
+ *
+ * @throws LdapLdifException If something went wrong
+ */
+ private void parse( Reader reader ) throws LdapSchemaException, IOException
+ {
+ PosSchema pos = new PosSchema();
+
+ while ( true )
+ {
+ // Always move forward to the next element, skipping whites, NL and comments
+ skipWhites( reader, pos, false );
+
+ if ( pos.line == null )
+ {
+ // The end, get out
+ break;
+ }
+
+ // Ok, we have something which must be one of openLdapObjectIdentifier( "objectidentifier" ),
+ // openLdapAttributeType ( "attributetype" ) or openLdapObjectClass ( "objectclass" )
+ if ( startsWith( pos, "objectidentifier" ) )
+ {
+ pos.start += "objectidentifier".length();
+
+ processObjectIdentifier( reader, pos );
+ }
+ else if ( startsWith( pos, "attributetype" ) )
+ {
+ pos.start += "attributetype".length();
+
+ MutableAttributeType attributeType = parseAttributeType( reader, pos );
+ schemaDescriptions.add( attributeType );
+ }
+ else if ( startsWith( pos, "objectclass" ) )
+ {
+ pos.start += "objectclass".length();
+
+ MutableObjectClass objectClass = parseObjectClass( reader, pos );
+ schemaDescriptions.add( objectClass );
+ }
+ else
+ {
+ // This is an error
+ throw new LdapSchemaException( I18n.err( I18n.ERR_13806_UNEXPECTED_ELEMENT_READ,
+ pos.line.substring( pos.start ), pos.lineNumber, pos.start ) );
+ }
+ }
+ }
+
+
+ /**
+ * Parses a file of OpenLDAP schemaObject elements/objects. Default charset is used.
+ *
+ * @param schemaFile a file of schema objects
+ * @throws IOException If the schemaObject can't be transformed to a byteArrayInputStream
+ * @throws ParseException If the schemaObject can't be parsed
+ */
+ public void parse( File schemaFile ) throws ParseException
+ {
+ try ( InputStream is = Files.newInputStream( Paths.get( schemaFile.getPath() ) ) )
+ {
+ try ( Reader reader = new BufferedReader( new InputStreamReader( is, Charset.defaultCharset() ) ) )
+ {
+ parse( reader );
+ afterParse();
+ }
+ catch ( LdapSchemaException | IOException e )
+ {
+ throw new ParseException( e.getMessage(), 0 );
+ }
+ }
+ catch ( IOException e )
+ {
+ String msg = I18n.err( I18n.ERR_13443_CANNOT_FIND_FILE, schemaFile.getAbsoluteFile() );
+ LOG.error( msg );
+ throw new ParseException( e.getMessage(), 0 );
+ }
+ }
+
+
+ /**
+ * Checks if object identifier macros should be resolved.
+ *
+ * @return true, object identifier macros should be resolved.
+ */
+ public boolean isResolveObjectIdentifierMacros()
+ {
+ return isResolveObjectIdentifierMacros;
+ }
+
+
+ /**
+ * Sets if object identifier macros should be resolved.
+ *
+ * @param resolveObjectIdentifierMacros true if object identifier macros should be resolved
+ */
+ public void setResolveObjectIdentifierMacros( boolean resolveObjectIdentifierMacros )
+ {
+ this.isResolveObjectIdentifierMacros = resolveObjectIdentifierMacros;
+ }
+
+ /**
+ * Checks if quirks mode is enabled.
+ *
+ * @return true, if is quirks mode is enabled
+ */
+ public boolean isQuirksMode()
+ {
+ return isQuirksModeEnabled;
+ }
+
+
+ /**
+ * Sets the quirks mode.
+ *
+ * If enabled the parser accepts non-numeric OIDs and some
+ * special characters in descriptions.
+ *
+ * @param enabled the new quirks mode
+ */
+ public void setQuirksMode( boolean enabled )
+ {
+ isQuirksModeEnabled = enabled;
+ }
+}
\ No newline at end of file
diff --git a/ldap/model/src/test/java/org/apache/directory/api/ldap/model/schema/parsers/OpenLdapSchemaParserTest.java b/ldap/model/src/test/java/org/apache/directory/api/ldap/model/schema/parsers/FastOpenLdapSchemaParserTest.java
similarity index 95%
copy from ldap/model/src/test/java/org/apache/directory/api/ldap/model/schema/parsers/OpenLdapSchemaParserTest.java
copy to ldap/model/src/test/java/org/apache/directory/api/ldap/model/schema/parsers/FastOpenLdapSchemaParserTest.java
index 0dcfbfb..303b9b1 100644
--- a/ldap/model/src/test/java/org/apache/directory/api/ldap/model/schema/parsers/OpenLdapSchemaParserTest.java
+++ b/ldap/model/src/test/java/org/apache/directory/api/ldap/model/schema/parsers/FastOpenLdapSchemaParserTest.java
@@ -25,6 +25,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import java.io.File;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
@@ -51,16 +52,16 @@ import com.mycila.junit.concurrent.ConcurrentJunitRunner;
*/
@RunWith(ConcurrentJunitRunner.class)
@Concurrency()
-public class OpenLdapSchemaParserTest
+public class FastOpenLdapSchemaParserTest
{
- private OpenLdapSchemaParser parser;
+ private FastOpenLdapSchemaParser parser;
@Before
public void setUp() throws Exception
{
- parser = new OpenLdapSchemaParser();
- parser.setParserMonitor( new ConsoleParserMonitor() );
+ parser = new FastOpenLdapSchemaParser();
+ // parser.setParserMonitor( new ConsoleParserMonitor() );
}
@@ -329,6 +330,7 @@ public class OpenLdapSchemaParserTest
public void testParseOpenLdapCoreSchema() throws Exception
{
InputStream input = getClass().getResourceAsStream( "core.schema" );
+ parser.setQuirksMode( true );
parser.parse( input );
List<MutableAttributeType> attributeTypes = parser.getAttributeTypes();
@@ -456,4 +458,25 @@ public class OpenLdapSchemaParserTest
assertEquals( "1.3.6.1.4.1.1466.115.121.1.15", attributeType.getSyntaxOid() );
assertTrue( attributeType.isSingleValued() );
}
+
+
+ @Test
+ public void testFastLdifParsePerf() throws Exception
+ {
+ FastOpenLdapSchemaParser parser = new FastOpenLdapSchemaParser();
+ parser.setQuirksMode( true );
+ long t0 = System.currentTimeMillis();
+
+ for ( int i = 0; i < 1_000; i++ )
+ {
+ try ( InputStream input = getClass().getResourceAsStream( "core.schema" ) )
+ {
+ parser.parse( input );
+ parser.clear();
+ }
+ }
+ long t1 = System.currentTimeMillis();
+
+ System.out.println( t1 - t0 );
+ }
}
diff --git a/ldap/model/src/test/java/org/apache/directory/api/ldap/model/schema/parsers/OpenLdapSchemaParserTest.java b/ldap/model/src/test/java/org/apache/directory/api/ldap/model/schema/parsers/OpenLdapSchemaParserTest.java
index 0dcfbfb..91eff91 100644
--- a/ldap/model/src/test/java/org/apache/directory/api/ldap/model/schema/parsers/OpenLdapSchemaParserTest.java
+++ b/ldap/model/src/test/java/org/apache/directory/api/ldap/model/schema/parsers/OpenLdapSchemaParserTest.java
@@ -25,6 +25,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import java.io.File;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
@@ -456,4 +457,25 @@ public class OpenLdapSchemaParserTest
assertEquals( "1.3.6.1.4.1.1466.115.121.1.15", attributeType.getSyntaxOid() );
assertTrue( attributeType.isSingleValued() );
}
+
+
+ @Test
+ public void testLdifParsePerf() throws Exception
+ {
+ OpenLdapSchemaParser parser = new OpenLdapSchemaParser();
+ parser.setQuirksMode( true );
+ long t0 = System.currentTimeMillis();
+
+ for ( int i = 0; i < 1_000; i++ )
+ {
+ try ( InputStream input = getClass().getResourceAsStream( "core.schema" ) )
+ {
+ parser.parse( input );
+ parser.clear();
+ }
+ }
+ long t1 = System.currentTimeMillis();
+
+ System.out.println( t1 - t0 );
+ }
}
--
To stop receiving notification emails like this one, please contact
elecharny@apache.org.