blob: d7d1d7131159e636d10fb873dc8e6b8dd6dc67e6 [file] [log] [blame]
/*
* 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
*
* https://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.asn1.util.Oid;
import org.apache.directory.api.i18n.I18n;
import org.apache.directory.api.ldap.model.exception.LdapSchemaException;
import org.apache.directory.api.ldap.model.schema.AttributeType;
import org.apache.directory.api.ldap.model.schema.DitContentRule;
import org.apache.directory.api.ldap.model.schema.DitStructureRule;
import org.apache.directory.api.ldap.model.schema.LdapSyntax;
import org.apache.directory.api.ldap.model.schema.MatchingRule;
import org.apache.directory.api.ldap.model.schema.MatchingRuleUse;
import org.apache.directory.api.ldap.model.schema.ObjectClass;
import org.apache.directory.api.ldap.model.schema.NameForm;
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 OpenLdapSchemaParser
{
/** The LoggerFactory used by this class */
protected static final Logger LOG = LoggerFactory.getLogger( OpenLdapSchemaParser.class );
/** 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<AttributeType> 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<>();
/** Some contant strings used in descriptions */
private static final String APPLIES_STR = "APPLIES";
private static final String ABSTRACT_STR = "ABSTRACT";
private static final String AUX_STR = "AUX";
private static final String AUXILIARY_STR = "AUXILIARY";
private static final String BYTECODE_STR = "BYTECODE";
private static final String COLLECTIVE_STR = "COLLECTIVE";
private static final String DESC_STR = "DESC";
private static final String EQUALITY_STR = "EQUALITY";
private static final String FORM_STR = "FORM";
private static final String FQCN_STR = "FQCN";
private static final String MAY_STR = "MAY";
private static final String MUST_STR = "MUST";
private static final String NAME_STR = "NAME";
private static final String NO_USER_MODIFICATION_STR = "NO-USER-MODIFICATION";
private static final String NOT_STR = "NOT";
private static final String OBSOLETE_STR = "OBSOLETE";
private static final String OC_STR = "OC";
private static final String ORDERING_STR = "ORDERING";
private static final String SINGLE_VALUE_STR = "SINGLE-VALUE";
private static final String STRUCTURAL_STR = "STRUCTURAL";
private static final String SUBSTR_STR = "SUBSTR";
private static final String SUP_STR = "SUP";
private static final String SYNTAX_STR = "SYNTAX";
private static final String USAGE_STR = "USAGE";
private static final String EXTENSION_PREFIX = "X-";
/** Usage */
private static final String DIRECTORY_OPERATION_STR = "directoryOperation";
private static final String DISTRIBUTED_OPERATION_STR = "distributedOperation";
private static final String DSA_OPERATION_STR = "dSAOperation";
private static final String USER_APPLICATIONS_STR = "userApplications";
/** Tokens */
private static final char COLON = ':';
private static final char DOLLAR = '$';
private static final char DOT = '.';
private static final char EQUAL = '=';
private static final char ESCAPE = '\\';
private static final char HYPHEN = '-';
private static final char LBRACE = '{';
private static final char LPAREN = '(';
private static final char PLUS = '+';
private static final char RBRACE = '}';
private static final char RPAREN = ')';
private static final char SEMI_COLON = ';';
private static final char SHARP = '#';
private static final char SLASH = '/';
private static final char SQUOTE = '\'';
private static final char UNDERSCORE = '_';
private static final char DQUOTE = '"';
/** Flag whether object identifier macros should be resolved. */
private boolean isResolveObjectIdentifierMacros;
private static final boolean UN_QUOTED = false;
/** Flag for strict or relaxed mode */
private static final boolean STRICT = false;
private static final boolean RELAXED = true;
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 );
}
}
}
private interface SchemaObjectElements
{
int getValue();
}
/**
* The list of AttributeTypeDescription elements that can be seen
*/
private enum AttributeTypeElements implements SchemaObjectElements
{
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;
}
public int getValue()
{
return value;
}
}
/**
* The list of DitContentRuleDescription elements that can be seen
*/
private enum DitContentRuleElements implements SchemaObjectElements
{
NAME(1),
DESC(2),
OBSOLETE(4),
AUX(8),
MUST(16),
MAY(32),
NOT(64);
private int value;
DitContentRuleElements( int value )
{
this.value = value;
}
public int getValue()
{
return value;
}
}
/**
* The list of DitStructureRuleDescription elements that can be seen
*/
private enum DitStructureRuleElements implements SchemaObjectElements
{
NAME(1),
DESC(2),
OBSOLETE(4),
FORM(8),
SUP(16);
private int value;
DitStructureRuleElements( int value )
{
this.value = value;
}
public int getValue()
{
return value;
}
}
/**
* The list of LdapComparatorDescription elements that can be seen
*/
private enum LdapComparatorElements implements SchemaObjectElements
{
DESC(1),
FQCN(2),
BYTECODE(4);
private int value;
LdapComparatorElements( int value )
{
this.value = value;
}
public int getValue()
{
return value;
}
}
/**
* The list of LdapSyntaxDescription elements that can be seen
*/
private enum LdapSyntaxElements implements SchemaObjectElements
{
DESC(1);
private int value;
LdapSyntaxElements( int value )
{
this.value = value;
}
public int getValue()
{
return value;
}
}
/**
* The list of MatchingRuleDescription elements that can be seen
*/
private enum MatchingRuleElements implements SchemaObjectElements
{
NAME(1),
DESC(2),
OBSOLETE(4),
SYNTAX(8);
private int value;
MatchingRuleElements( int value )
{
this.value = value;
}
public int getValue()
{
return value;
}
}
/**
* The list of MatchingRuleUseDescription elements that can be seen
*/
private enum MatchingRuleUseElements implements SchemaObjectElements
{
NAME(1),
DESC(2),
OBSOLETE(4),
APPLIES(8);
private int value;
MatchingRuleUseElements( int value )
{
this.value = value;
}
public int getValue()
{
return value;
}
}
/**
* The list of NameFormDescription elements that can be seen
*/
private enum NameFormElements implements SchemaObjectElements
{
NAME(1),
DESC(2),
OBSOLETE(4),
OC(8),
MUST(16),
MAY(32);
private int value;
NameFormElements( int value )
{
this.value = value;
}
public int getValue()
{
return value;
}
}
/**
* The list of NormalizerDescription elements that can be seen
*/
private enum NormalizerElements implements SchemaObjectElements
{
DESC(1),
FQCN(2),
BYTECODE(4);
private int value;
NormalizerElements( int value )
{
this.value = value;
}
public int getValue()
{
return value;
}
}
/**
* The list of ObjectClassDescription elements that can be seen
*/
private enum ObjectClassElements implements SchemaObjectElements
{
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;
}
public int getValue()
{
return value;
}
}
/**
* The list of SyntaxCheckerDescription elements that can be seen
*/
private enum SyntaxCheckerElements implements SchemaObjectElements
{
DESC(1),
FQCN(2),
BYTECODE(4);
private int value;
SyntaxCheckerElements( int value )
{
this.value = value;
}
public int getValue()
{
return value;
}
}
/**
* Creates a reusable instance of an OpenLdapSchemaParser.
*/
public OpenLdapSchemaParser()
{
isResolveObjectIdentifierMacros = true;
isQuirksModeEnabled = false;
}
/**
* 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<AttributeType> getAttributeTypes()
{
return attributeTypes;
}
/**
* Gets the object class types.
*
* @return the object class types
*/
public List<ObjectClass> getObjectClasses()
{
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 )
{
AttributeType attributeType = ( AttributeType ) 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 ( AttributeType attributeType : attributeTypes )
{
attributeType.setOid( getResolveOid( attributeType.getOid() ) );
attributeType.setSyntaxOid( getResolveOid( attributeType.getSyntaxOid() ) );
}
}
}
/**
* Return a complete OID from a macro followed by an OID.
*
* @param oid The OID to find
* @return The extended OID
*/
private String getResolveOid( String oid )
{
if ( oid != null && oid.indexOf( COLON ) != -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;
}
/**
* Find the proper OID from a OID which may contain a macro
*
* @param macro The element to resolve
* @throws ParseException If teh OID is invalid
*/
private void resolveObjectIdentifierMacro( OpenLdapObjectIdentifierMacro macro ) throws ParseException
{
String rawOidOrNameSuffix = macro.getRawOidOrNameSuffix();
if ( !macro.isResolved() )
{
if ( rawOidOrNameSuffix.indexOf( COLON ) != -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 ParseException If the schema can't be parsed
* @throws LdapSchemaException If there is an error in the schema
* @throws IOException If the stream can't be read
*/
public void parse( InputStream schemaIn ) throws ParseException, LdapSchemaException, IOException
{
try ( InputStreamReader in = new InputStreamReader( schemaIn, Charset.defaultCharset() ) )
{
try ( Reader reader = new BufferedReader( in ) )
{
parse( reader );
afterParse();
}
}
}
/**
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param mandatory If the spaces are mandatory
* @throws IOException If the stream can't be read
* @throws LdapSchemaException If the schema is wrong
*/
private static 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;
}
}
}
if ( pos.line.charAt( pos.start ) == SHARP )
{
getLine( reader, pos );
if ( pos.line == null )
{
return;
}
hasSpace = true;
}
else
{
if ( mandatory && !hasSpace )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13783_SPACE_EXPECTED, pos.lineNumber, pos.start ) );
}
else
{
return;
}
}
}
}
/**
* @param pos The position in the Schema
* @return <tt>true</tt> if this is a comment
*/
private static boolean isComment( PosSchema pos )
{
if ( isEmpty( pos ) )
{
return true;
}
return pos.line.charAt( pos.start ) == SHARP;
}
/**
* @param pos The position in the Schema
* @return <tt>true</tt> of the line is empty
*/
private static boolean isEmpty( PosSchema pos )
{
return ( pos.line == null ) || ( pos.start >= pos.line.length() );
}
/**
* @param pos The position in the Schema
* @param text The text to find at the beginning of the line
* @return <tt>true</tt> if teh line starts with the given text
*/
private static 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() ) );
}
/**
* Check if the stream starts with a given char at a given position
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param c The char to check
* @return <tt>true</tT> if the stream starts with the given char at the given position
* @throws IOException If we can't read the stream
* @throws LdapSchemaException If we have no char to read
*/
private static boolean startsWith( Reader reader, PosSchema pos, char c ) throws IOException, LdapSchemaException
{
return startsWith( reader, pos, c, UN_QUOTED );
}
/**
* Check if the stream at the given position starts with a given char
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param c The char to check
* @param quoted <tt>true</tt> if the char is quoted
* @return <tt>true</tt> if the stream starts with the given char at the given position
* @throws IOException If we can't read the stream
* @throws LdapSchemaException If we have no char to read
*/
private static 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;
}
/**
* @param pos The position in the Schema
* @param c The char to find at the beginning of the line
* @return <tt>true</tt> if the char is found at the beginning of the line
*/
private static boolean startsWith( PosSchema pos, char c )
{
if ( ( pos.line == null ) || ( pos.line.length() - pos.start < 1 ) )
{
return false;
}
return pos.line.charAt( pos.start ) == c;
}
/**
* @param pos The position in the Schema
* @return <tt>true</tt> if the first char is alphabetic
*/
private static boolean isAlpha( PosSchema pos )
{
return Character.isAlphabetic( pos.line.charAt( pos.start ) );
}
/**
* @param pos The position in the Schema
* @return <tt>true</tt> if the first char is a digit
*/
private static boolean isDigit( PosSchema pos )
{
return Character.isDigit( pos.line.charAt( pos.start ) );
}
/**
*
* @param reader The stream reader
* @param pos The position in the Schema
* @throws IOException If the stream can't be read
*/
private static void getLine( Reader reader, PosSchema pos ) throws IOException
{
pos.line = ( ( BufferedReader ) reader ).readLine();
pos.start = 0;
if ( pos.line != null )
{
pos.lineNumber++;
}
}
/**
* <pre>
* numericoid ::= number ( DOT number )+
* number ::= DIGIT | LDIGIT DIGIT+
* DIGIT ::= %x30 | LDIGIT ; "0"-"9"
* LDIGIT ::= %x31-39 ; "1"-"9"
* DOT ::= %x2E ; period (".")
* </pre>
*
* @param pos The position in the Schema
* @return The numeric OID
* @throws LdapSchemaException If the schema is wrong
*/
private static String getNumericOid( PosSchema pos ) throws LdapSchemaException
{
int start = pos.start;
boolean isDot = false;
boolean isFirstZero = false;
boolean isFirstDigit = true;
while ( !isEmpty( pos ) )
{
char c = pos.line.charAt( pos.start );
if ( Character.isDigit( c ) )
{
if ( isFirstZero )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13784_BAD_OID_TWO_ZEROES, pos.lineNumber, pos.start ) );
}
if ( ( pos.line.charAt( pos.start ) == '0' ) && isFirstDigit )
{
isFirstZero = true;
}
isDot = false;
pos.start++;
isFirstDigit = false;
}
else if ( c == DOT )
{
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 ) );
}
isFirstZero = 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 ) );
}
String oidStr = pos.line.substring( start, pos.start );
if ( Oid.isOid( oidStr ) )
{
return oidStr;
}
else
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.line, pos.start ) );
}
}
/**
* <pre>
* partialNumericoid ::= number ( DOT number )*
* number ::= DIGIT | LDIGIT DIGIT+
* DIGIT ::= %x30 | LDIGIT ; "0"-"9"
* LDIGIT ::= %x31-39 ; "1"-"9"
* DOT ::= %x2E ; period (".")
* </pre>
*
* @param pos The position in the Schema
* @return The found OID
* @throws LdapSchemaException If the schema is wrong
*/
private static String getPartialNumericOid( PosSchema pos ) throws LdapSchemaException
{
int start = pos.start;
boolean isDot = false;
boolean isFirstZero = false;
boolean isFirstDigit = true;
while ( !isEmpty( pos ) )
{
if ( isDigit( pos ) )
{
if ( isFirstZero )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13784_BAD_OID_TWO_ZEROES, pos.lineNumber, pos.start ) );
}
if ( ( pos.line.charAt( pos.start ) == '0' ) && isFirstDigit )
{
isFirstZero = true;
}
isDot = false;
pos.start++;
isFirstDigit = false;
}
else if ( startsWith( pos, DOT ) )
{
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 ) );
}
isFirstZero = 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 relaxed 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>
*
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return The found OID
* @throws LdapSchemaException If the schema is wrong
*/
private static String getOidAndMacroRelaxed( PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws LdapSchemaException
{
if ( isEmpty( pos ) )
{
return "";
}
// This is a OID name
int start = pos.start;
char c = pos.line.charAt( pos.start );
boolean isDigit = Character.isDigit( c );
while ( isDigit || Character.isAlphabetic( c ) || ( c == HYPHEN ) || ( c == UNDERSCORE )
|| ( c == SEMI_COLON ) || ( c == DOT ) || ( c == SHARP ) )
{
pos.start++;
if ( isEmpty( pos ) )
{
break;
}
c = pos.line.charAt( pos.start );
isDigit = Character.isDigit( c );
}
String oidName = pos.line.substring( start, pos.start );
if ( Strings.isEmpty( oidName ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
}
// We may have a ':' followed by an OID
if ( startsWith( pos, COLON ) )
{
pos.start++;
String oid = getPartialNumericOid( pos );
return objectIdentifierMacros.get( oidName ).getRawOidOrNameSuffix() + DOT + oid;
}
else
{
// Ok, we may just have an oidName
OpenLdapObjectIdentifierMacro macro = objectIdentifierMacros.get( oidName );
if ( macro == null )
{
return oidName;
}
else
{
return macro.getRawOidOrNameSuffix();
}
}
}
/**
* 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>
*
* @param pos The position in the Schema
* @return The found OID
* @throws LdapSchemaException If the schema is wrong
*/
private static String getOidStrict( PosSchema pos ) throws LdapSchemaException
{
if ( isEmpty( pos ) )
{
return "";
}
if ( isAlpha( pos ) )
{
// A descr
return getDescrStrict( pos );
}
else if ( isDigit( pos ) )
{
// This is a numeric oid
return getNumericOid( pos );
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
}
}
/**
* In quirks mode :
* <pre>
* oid ::= descr-relaxed | numericoid | SQUOTE descr-relaxed SQUOTE |
* DQUOTE descr-relaxed DQUOTE | SQUOTE numericoid SQUOTE |
* DQUOTE numericoid DQUOTE
* descr-relaxed::= macro (COLON numericoid)
* macro ::= keystring
* keystring ::= Lkeychar keychar*
* Lkeychar ::= ALPHA | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP
* keychar ::= 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>
*
* @param pos The position in the Schema
* @param hadQuote If we have had a quote
* @return the found OID
* @throws LdapSchemaException If the schema is wrong
*/
private static String getOidRelaxed( PosSchema pos, boolean hadQuote ) throws LdapSchemaException
{
if ( isEmpty( pos ) )
{
return "";
}
boolean hasQuote = false;
char c = pos.line.charAt( pos.start );
if ( c == SQUOTE )
{
if ( hadQuote )
{
return "";
}
hasQuote = true;
pos.start++;
if ( isEmpty( pos ) )
{
return "";
}
c = pos.line.charAt( pos.start );
}
String oid;
if ( Character.isAlphabetic( c ) )
{
// This is a OID name
oid = getDescrRelaxed( pos );
}
else if ( Character.isDigit( c ) )
{
// This is a numeric oid
oid = getNumericOid( pos );
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED,
pos.lineNumber, pos.start ) );
}
if ( isEmpty( pos ) )
{
if ( hasQuote || hadQuote )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END,
pos.lineNumber, pos.start ) );
}
else
{
return oid;
}
}
c = pos.line.charAt( pos.start );
if ( ( c == SQUOTE ) && !hadQuote )
{
if ( hasQuote )
{
pos.start++;
}
else
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END,
pos.lineNumber, pos.start ) );
}
}
return oid;
}
/**
* In strict 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>
*
* @param pos The position in the Schema
* @return The descr
* @throws LdapSchemaException If the schema is wrong
*/
private static String getDescrStrict( PosSchema pos ) throws LdapSchemaException
{
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 == HYPHEN ) )
{
pos.start++;
}
else
{
// We are done
return pos.line.substring( start, pos.start );
}
}
}
return pos.line.substring( start, pos.start );
}
/**
* 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>
*
* @param pos The position in the Schema
* @return The descr
* @throws LdapSchemaException If the schema is wrong
*/
private static String getDescrRelaxed( PosSchema pos ) throws LdapSchemaException
{
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 == HYPHEN ) || ( c == UNDERSCORE )
|| ( c == SEMI_COLON ) || ( c == DOT ) || ( c == COLON ) || ( c == SHARP ) )
{
// 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 == HYPHEN )
|| ( c == UNDERSCORE ) || ( c == SEMI_COLON ) || ( c == DOT ) || ( c == COLON ) || ( c == SHARP ) )
{
pos.start++;
}
else
{
// We are done
return pos.line.substring( start, pos.start );
}
}
}
return pos.line.substring( start, pos.start );
}
/**
*
* @param pos The position in the Schema
* @return The found macro, if any
* @throws LdapSchemaException If the schema is wrong
*/
private String getMacro( PosSchema pos ) throws LdapSchemaException
{
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 == HYPHEN ) || ( c == UNDERSCORE )
|| ( c == SEMI_COLON ) || ( c == DOT ) || ( c == SHARP ) )
{
// 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 == HYPHEN )
|| ( c == UNDERSCORE ) || ( c == SEMI_COLON ) || ( c == DOT ) || ( c == SHARP ) )
{
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 == HYPHEN ) )
{
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>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @return The QDescr
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static String getQDescrStrict( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
{
// The first quote
if ( !startsWith( reader, pos, SQUOTE ) )
{
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( pos, SQUOTE ) )
{
if ( isFirst )
{
isFirst = false;
if ( !isEmpty( pos ) && 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
{
if ( isEmpty( pos ) )
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END,
pos.lineNumber, pos.start ) );
}
char c = pos.line.charAt( pos.start );
if ( Character.isAlphabetic( c ) || Character.isDigit( c ) || ( c == HYPHEN ) )
{
pos.start++;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13791_KEYCHAR_EXPECTED, c,
pos.lineNumber, pos.start ) );
}
}
}
if ( startsWith( pos, SQUOTE ) )
{
// 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>
* 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>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @return the QDescr
* @throws IOException If the stream can't be read
* @throws LdapSchemaException If the schema is wrong
*/
private static String getQDescrRelaxed( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
{
if ( startsWith( reader, pos, SQUOTE ) )
{
pos.start++;
int start = pos.start;
while ( !startsWith( pos, SQUOTE ) )
{
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 == HYPHEN ) || ( c == UNDERSCORE )
|| ( c == SEMI_COLON ) || ( c == DOT ) || ( c == COLON ) || ( c == SHARP ) )
{
pos.start++;
}
else if ( c != SQUOTE )
{
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 == HYPHEN ) || ( c == UNDERSCORE )
|| ( c == SEMI_COLON ) || ( c == DOT ) || ( c == COLON ) || ( c == SHARP ) )
{
pos.start++;
}
else
{
break;
}
}
return pos.line.substring( start, pos.start );
}
}
/**
* No relaxed version.
* <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>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @return The QDString
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static String getQDString( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
{
// The first quote
if ( !startsWith( reader, pos, SQUOTE ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13789_SIMPLE_QUOTE_EXPECTED_AT_START,
pos.lineNumber, pos.start ) );
}
pos.start++;
int start = pos.start;
int nbEscapes = 0;
while ( !isEmpty( pos ) && !startsWith( pos, SQUOTE ) )
{
// At the moment, just swallow anything
if ( startsWith( pos, ESCAPE ) )
{
nbEscapes++;
}
pos.start++;
}
if ( startsWith( pos, SQUOTE ) )
{
// We are done, move one char forward to eliminate the simple quote
pos.start++;
// Now, un-escape the escaped chars
char[] unescaped = new char[pos.start - 1 - start - nbEscapes * 2];
int newPos = 0;
for ( int i = start; i < pos.start - 1; i++ )
{
char c = pos.line.charAt( i );
if ( c == ESCAPE )
{
if ( i + 2 > pos.start )
{
// Error : not enough hex value
throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END,
pos.lineNumber, pos.start ) );
}
int u = Character.digit( pos.line.charAt( i + 1 ), 16 );
int l = Character.digit( pos.line.charAt( i + 2 ), 16 );
unescaped[newPos] = ( char ) ( ( u << 4 ) + l );
i += 2;
}
else
{
unescaped[newPos] = c;
}
newPos++;
}
return new String( unescaped );
}
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>
* 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
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param relaxed If the schema is to be processed in relaxed mode
* @return The list of QDescr
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static List<String> getQDescrs( Reader reader, PosSchema pos, boolean relaxed )
throws LdapSchemaException, IOException
{
List<String> qdescrs = new ArrayList<>();
// It may start with a '('
if ( startsWith( reader, pos, LPAREN ) )
{
pos.start++;
// We have more than a name
skipWhites( reader, pos, false );
while ( !startsWith( reader, pos, RPAREN ) )
{
String qdescr;
if ( relaxed )
{
qdescr = getQDescrRelaxed( reader, pos );
}
else
{
qdescr = getQDescrStrict( reader, pos );
}
qdescrs.add( qdescr );
if ( startsWith( reader, pos, RPAREN ) )
{
break;
}
skipWhites( reader, pos, true );
}
if ( !startsWith( reader, pos, RPAREN ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13793_NO_CLOSING_PAREN,
pos.lineNumber, pos.start ) );
}
pos.start++;
}
else
{
// Only one name, read it
String qDescr;
if ( relaxed )
{
qDescr = getQDescrRelaxed( reader, pos );
}
else
{
qDescr = getQDescrStrict( reader, pos );
}
if ( Strings.isEmpty( qDescr ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13732_NAME_CANNOT_BE_NULL, pos.lineNumber, pos.start ) );
}
qdescrs.add( qDescr );
}
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>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @return The list of QDString
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static List<String> getQDStrings( Reader reader, PosSchema pos )
throws LdapSchemaException, IOException
{
List<String> qdStrings = new ArrayList<>();
// It may start with a '('
if ( startsWith( reader, pos, LPAREN ) )
{
pos.start++;
// We have more than a name
skipWhites( reader, pos, false );
while ( !startsWith( reader, pos, RPAREN ) )
{
qdStrings.add( getQDString( reader, pos ) );
if ( startsWith( reader, pos, RPAREN ) )
{
break;
}
skipWhites( reader, pos, true );
}
if ( !startsWith( reader, pos, RPAREN ) )
{
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>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @return The list of OIDs
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static List<String> getOidsStrict( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
{
List<String> oids = new ArrayList<>();
// It may start with a '('
if ( startsWith( reader, pos, LPAREN ) )
{
pos.start++;
// We have more than a name
skipWhites( reader, pos, false );
boolean moreExpected = false;
while ( !startsWith( reader, pos, RPAREN ) )
{
moreExpected = false;
oids.add( getOidStrict( pos ) );
if ( startsWith( reader, pos, RPAREN ) )
{
break;
}
skipWhites( reader, pos, false );
if ( startsWith( reader, pos, DOLLAR ) )
{
pos.start++;
moreExpected = true;
}
skipWhites( reader, pos, false );
}
if ( !startsWith( reader, pos, RPAREN ) )
{
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( getOidStrict( pos ) );
}
return oids;
}
/**
* <pre>
* oids ::= oid | ( LPAREN WSP oidlist WSP RPAREN )
* oidlist ::= oid *( WSP DOLLAR WSP oid )
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @return The list of OIDs
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static List<String> getOidsRelaxed( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
{
List<String> oids = new ArrayList<>();
// It may start with a '('
if ( startsWith( reader, pos, LPAREN ) )
{
pos.start++;
// We have more than a name
skipWhites( reader, pos, false );
boolean moreExpected = false;
while ( !startsWith( reader, pos, RPAREN ) )
{
moreExpected = false;
oids.add( getOidRelaxed( pos, UN_QUOTED ) );
if ( startsWith( reader, pos, RPAREN ) )
{
break;
}
skipWhites( reader, pos, false );
if ( startsWith( reader, pos, DOLLAR ) )
{
pos.start++;
moreExpected = true;
}
skipWhites( reader, pos, false );
}
if ( !startsWith( reader, pos, RPAREN ) )
{
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( getOidRelaxed( pos, UN_QUOTED ) );
}
return oids;
}
/**
* <pre>
* noidlen = oidStrict [ LCURLY len RCURLY ]
* </pre>
*
* @param attributeType The AttributeType
* @param pos The position in the Schema
* @throws LdapSchemaException If the schema is wrong
*/
private static void getNoidLenStrict( AttributeType attributeType, PosSchema pos ) throws LdapSchemaException
{
// Get the oid
String oid = getOidStrict( pos );
if ( oid.length() == 0 )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13828_MISSING_SYNTAX_OID, pos.line, pos.start ) );
}
attributeType.setSyntaxOid( oid );
// Then the len, if any
if ( startsWith( pos, LBRACE ) )
{
pos.start++;
int start = pos.start;
while ( !isEmpty( pos ) && isDigit( pos ) )
{
pos.start++;
}
if ( startsWith( pos, RBRACE ) )
{
String lenStr = pos.line.substring( start, pos.start );
if ( lenStr.length() == 0 )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13827_EMPTY_SYNTAX_LEN, pos.line, pos.start ) );
}
pos.start++;
if ( Strings.isEmpty( lenStr ) )
{
attributeType.setSyntaxLength( -1L );
}
else
{
attributeType.setSyntaxLength( Long.parseLong( 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 ) );
}
}
}
/**
* <pre>
* noidlen = oidRelaxed [ LCURLY len RCURLY ]
* </pre>
*
* @param attributeType The AttributeType
* @param pos The position in the Schema
* @throws LdapSchemaException If the schema is wrong
*/
private static void getNoidLenRelaxed( AttributeType attributeType, PosSchema pos ) throws LdapSchemaException
{
// Check for quotes
boolean hasQuote = false;
char c = pos.line.charAt( pos.start );
if ( c == SQUOTE )
{
hasQuote = true;
pos.start++;
if ( isEmpty( pos ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END,
pos.lineNumber, pos.start ) );
}
}
// Get the oid
String oid = getOidRelaxed( pos, hasQuote );
if ( oid.length() == 0 )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13828_MISSING_SYNTAX_OID, pos.line, pos.start ) );
}
attributeType.setSyntaxOid( oid );
// Then the len, if any
if ( startsWith( pos, LBRACE ) )
{
pos.start++;
int start = pos.start;
while ( !isEmpty( pos ) && isDigit( pos ) )
{
pos.start++;
}
if ( startsWith( pos, RBRACE ) )
{
String lenStr = pos.line.substring( start, pos.start );
pos.start++;
if ( Strings.isEmpty( lenStr ) )
{
attributeType.setSyntaxLength( -1L );
}
else
{
attributeType.setSyntaxLength( Long.parseLong( 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 ) );
}
}
if ( hasQuote )
{
if ( isEmpty( pos ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END,
pos.lineNumber, pos.start ) );
}
c = pos.line.charAt( pos.start );
if ( c == SQUOTE )
{
pos.start++;
}
else
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END,
pos.lineNumber, pos.start ) );
}
}
}
/**
* <pre>
* ruleid ::= number
* number ::= DIGIT | LDIGIT DIGIT+
* DIGIT ::= [0-9]
* LDIGIT ::= [1-9]
* </pre>
*
* @param pos The position in the Schema
* @return The RuleID
* @throws LdapSchemaException If the schema is wrong
*/
private static int getRuleId( PosSchema pos ) throws LdapSchemaException
{
int start = pos.start;
while ( !isEmpty( pos ) && isDigit( pos ) )
{
pos.start++;
}
if ( start == pos.start )
{
// No ruleID
throw new LdapSchemaException( I18n.err( I18n.ERR_13811_INVALID_RULE_ID,
pos.lineNumber, pos.start ) );
}
String lenStr = pos.line.substring( start, pos.start );
return Integer.parseInt( lenStr );
}
/**
* <pre>
* ruleids ::= ruleid | ( LPAREN WSP ruleidlist WSP RPAREN )
* ruleidlist ::= ruleid ( SP ruleid )*
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @return The list of RuleIDs
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static List<Integer> getRuleIds( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException
{
List<Integer> ruleIds = new ArrayList<>();
// It may start with a '('
if ( startsWith( reader, pos, LPAREN ) )
{
pos.start++;
// We may have more than a ruleid
skipWhites( reader, pos, false );
boolean moreExpected = false;
while ( !startsWith( reader, pos, RPAREN ) )
{
moreExpected = false;
ruleIds.add( getRuleId( pos ) );
if ( startsWith( reader, pos, RPAREN ) )
{
break;
}
skipWhites( reader, pos, false );
if ( startsWith( reader, pos, DOLLAR ) )
{
pos.start++;
moreExpected = true;
}
skipWhites( reader, pos, false );
}
if ( !startsWith( reader, pos, RPAREN ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13793_NO_CLOSING_PAREN,
pos.lineNumber, pos.start ) );
}
if ( moreExpected )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13813_MORE_RULE_IDS_EXPECTED,
pos.lineNumber, pos.start ) );
}
pos.start++;
}
else
{
// Only one ruleId, read it
ruleIds.add( getRuleId( pos ) );
}
return ruleIds;
}
/**
*
* @param pos The position in the Schema
* @return The USAGE
* @throws LdapSchemaException If the schema is wrong
*/
private static UsageEnum getUsageStrict( PosSchema pos ) throws LdapSchemaException
{
if ( isEmpty( pos ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED,
pos.lineNumber, pos.start ) );
}
if ( startsWith( pos, USER_APPLICATIONS_STR ) )
{
pos.start += USER_APPLICATIONS_STR.length();
return UsageEnum.USER_APPLICATIONS;
}
else if ( startsWith( pos, DIRECTORY_OPERATION_STR ) )
{
pos.start += DIRECTORY_OPERATION_STR.length();
return UsageEnum.DIRECTORY_OPERATION;
}
else if ( startsWith( pos, DISTRIBUTED_OPERATION_STR ) )
{
pos.start += DISTRIBUTED_OPERATION_STR.length();
return UsageEnum.DISTRIBUTED_OPERATION;
}
else if ( startsWith( pos, DSA_OPERATION_STR ) )
{
pos.start += DSA_OPERATION_STR.length();
return UsageEnum.DSA_OPERATION;
}
else
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13797_USAGE_UNKNOWN,
pos.lineNumber, pos.start ) );
}
}
/**
*
* @param pos The position in the Schema
* @return The USAGE
* @throws LdapSchemaException If the schema is wrong
*/
private static UsageEnum getUsageRelaxed( PosSchema pos ) throws LdapSchemaException
{
if ( isEmpty( pos ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED,
pos.lineNumber, pos.start ) );
}
boolean isSQuoted = false;
boolean isDQuoted = false;
if ( pos.line.charAt( pos.start ) == SQUOTE )
{
isSQuoted = true;
pos.start++;
if ( isEmpty( pos ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED,
pos.lineNumber, pos.start ) );
}
}
else if ( pos.line.charAt( pos.start ) == DQUOTE )
{
isDQuoted = true;
pos.start++;
if ( isEmpty( pos ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED,
pos.lineNumber, pos.start ) );
}
}
UsageEnum usage = UsageEnum.USER_APPLICATIONS;
if ( startsWith( pos, USER_APPLICATIONS_STR ) )
{
pos.start += USER_APPLICATIONS_STR.length();
usage = UsageEnum.USER_APPLICATIONS;
}
else if ( startsWith( pos, DIRECTORY_OPERATION_STR ) )
{
pos.start += DIRECTORY_OPERATION_STR.length();
usage = UsageEnum.DIRECTORY_OPERATION;
}
else if ( startsWith( pos, DISTRIBUTED_OPERATION_STR ) )
{
pos.start += DISTRIBUTED_OPERATION_STR.length();
usage = UsageEnum.DISTRIBUTED_OPERATION;
}
else if ( startsWith( pos, DSA_OPERATION_STR ) )
{
pos.start += DSA_OPERATION_STR.length();
usage = UsageEnum.DSA_OPERATION;
}
else
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13797_USAGE_UNKNOWN,
pos.lineNumber, pos.start ) );
}
if ( isSQuoted )
{
if ( isEmpty( pos ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED,
pos.lineNumber, pos.start ) );
}
if ( pos.line.charAt( pos.start ) != SQUOTE )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END,
pos.lineNumber, pos.start ) );
}
pos.start++;
}
else if ( isDQuoted )
{
if ( isEmpty( pos ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED,
pos.lineNumber, pos.start ) );
}
if ( pos.line.charAt( pos.start ) != DQUOTE )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END,
pos.lineNumber, pos.start ) );
}
pos.start++;
}
return usage;
}
/**
* <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>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param schemaObject The SchemaObject
* @throws IOException If the stream can't be read
* @throws LdapSchemaException If the schema is wrong
*/
private static void processExtension( Reader reader, PosSchema pos, SchemaObject schemaObject )
throws LdapSchemaException, IOException
{
// The xstring first
String extensionKey = getXString( pos );
skipWhites( reader, pos, true );
List<String> extensionValues = getQDStrings( reader, pos );
if ( schemaObject.hasExtension( extensionKey ) )
{
throw new LdapSchemaException(
I18n.err( I18n.ERR_13780_SCHEMA_OBJECT_DESCRIPTION_HAS_ELEMENT_TWICE, extensionKey,
pos.lineNumber, pos.start ) );
}
schemaObject.addExtension( extensionKey, extensionValues );
}
/**
* <pre>
* xstring ::= "X" HYPHEN ( ALPHA | HYPHEN | USCORE )+
* </pre>
*
* @param pos The position in the Schema
* @return the X-String
* @throws LdapSchemaException If the schema is wrong
*/
private static String getXString( PosSchema pos ) throws LdapSchemaException
{
int start = pos.start;
if ( startsWith( pos, EXTENSION_PREFIX ) )
{
pos.start += 2;
// Now parse the remaining string
while ( !isEmpty( pos ) && ( isAlpha( pos ) || startsWith( pos, HYPHEN ) || startsWith( pos, UNDERSCORE ) ) )
{
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 ) );
}
}
/**
* A FQCN
* <pre>
* FQCN ::= FQCN_IDENTIFIER ( '.' FQCN_IDENTIFIER )*
* FQCN_IDENTIFIER ::= ( JavaLetter ( JavaLetterOrDigit )*
* </pre>
*
* @param pos The position in the Schema
* @return The FQCN
* @throws LdapSchemaException If the schema is wrong
*/
private static String getFqcn( PosSchema pos ) throws LdapSchemaException
{
if ( ( pos.line == null ) || ( pos.line.length() - pos.start < 1 ) )
{
return "";
}
int start = pos.start;
boolean isFirst = true;
boolean dotSeen = false;
while ( true )
{
char c = pos.line.charAt( pos.start );
if ( isFirst )
{
if ( !Character.isJavaIdentifierStart( c ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13822_INVALID_FQCN_BAD_IDENTIFIER_START,
pos.lineNumber, pos.start ) );
}
isFirst = false;
dotSeen = false;
pos.start++;
}
else
{
if ( c == DOT )
{
if ( dotSeen )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13823_INVALID_FQCN_DOUBLE_DOT,
pos.lineNumber, pos.start ) );
}
else
{
isFirst = true;
dotSeen = true;
pos.start++;
}
}
else
{
if ( Character.isJavaIdentifierPart( c ) )
{
pos.start++;
dotSeen = false;
}
else
{
return pos.line.substring( start, pos.start );
}
}
}
if ( pos.line.length() - pos.start < 1 )
{
return pos.line.substring( start, pos.start );
}
}
}
/**
* A base64 string
* <pre>
* byteCode ::= ( [a-z] | [A-Z] | [0-9] | '+' | '/' | '=' )*
* </pre>
*
* @param pos The position in the Schema
* @return The ByteCode
*/
private static String getByteCode( PosSchema pos )
{
if ( ( pos.line == null ) || ( pos.line.length() - pos.start < 1 ) )
{
return "";
}
int start = pos.start;
while ( !isEmpty( pos ) && ( isAlpha( pos ) || isDigit( pos ) || startsWith( pos, PLUS )
|| startsWith( pos, SLASH ) || startsWith( pos, EQUAL ) ) )
{
pos.start++;
if ( ( pos.line == null ) || ( pos.line.length() - pos.start < 1 ) )
{
return pos.line.substring( start, pos.start );
}
}
return pos.line.substring( start, pos.start );
}
/**
*
* @param elementsSeen The elements that have been processed already
* @param element The current element
* @param pos T he position in the Schema
* @return The elements we have just processed
* @throws LdapSchemaException If the schema is wrong
*/
private static int checkElement( int elementsSeen, SchemaObjectElements element, PosSchema pos ) throws LdapSchemaException
{
if ( ( elementsSeen & element.getValue() ) != 0 )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13780_SCHEMA_OBJECT_DESCRIPTION_HAS_ELEMENT_TWICE,
element, pos.lineNumber, pos.start ) );
}
elementsSeen |= element.getValue();
return elementsSeen;
}
/**
* 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>
*
* @param attributeTypeDescription The String containing the AttributeTypeDescription
* @return An instance of AttributeType
* @throws ParseException If the element was invalid
*/
public AttributeType parseAttributeType( String attributeTypeDescription ) throws ParseException
{
if ( ( attributeTypeDescription == null ) || Strings.isEmpty( attributeTypeDescription.trim() ) )
{
throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
}
try ( Reader reader = new BufferedReader( new StringReader( attributeTypeDescription ) ) )
{
PosSchema pos = new PosSchema();
if ( isQuirksModeEnabled )
{
return parseAttributeTypeRelaxed( reader, pos, objectIdentifierMacros );
}
else
{
return parseAttributeTypeStrict( reader, pos, objectIdentifierMacros );
}
}
catch ( IOException | LdapSchemaException e )
{
// This exception is not passed as a cause in ParseException. Therefore at least log in, so it won't be lost.
LOG.trace( I18n.err( I18n.ERR_13865_ERROR_PARSING_AT, attributeTypeDescription, e.getMessage() ), e );
throw new ParseException( e.getMessage(), 0 );
}
}
/**
* Production for matching attribute type descriptions. It is fault-tolerant
* against element ordering. It's strict.
*
* <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>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of AttributeType
* @throws IOException If the stream can't be read
* @throws LdapSchemaException If the schema is wrong
*/
private static AttributeType parseAttributeTypeStrict( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the OID.
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
// Check that the OID is valid
if ( !Oid.isOid( oid ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
}
AttributeType attributeType = new AttributeType( oid );
boolean hasSup = false;
boolean hasSyntax = false;
int elementsSeen = 0;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, NAME_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.NAME, pos );
pos.start += NAME_STR.length();
skipWhites( reader, pos, true );
attributeType.setNames( getQDescrs( reader, pos, STRICT ) );
}
else if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
attributeType.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, OBSOLETE_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.OBSOLETE, pos );
pos.start += OBSOLETE_STR.length();
attributeType.setObsolete( true );
}
else if ( startsWith( pos, SUP_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SUP, pos );
pos.start += SUP_STR.length();
skipWhites( reader, pos, true );
String superiorOid = getOidStrict( pos );
attributeType.setSuperiorOid( superiorOid );
hasSup = true;
}
else if ( startsWith( pos, EQUALITY_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.EQUALITY, pos );
pos.start += EQUALITY_STR.length();
skipWhites( reader, pos, true );
String equalityOid = getOidStrict( pos );
attributeType.setEqualityOid( equalityOid );
}
else if ( startsWith( pos, ORDERING_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.ORDERING, pos );
pos.start += ORDERING_STR.length();
skipWhites( reader, pos, true );
String orderingOid = getOidStrict( pos );
attributeType.setOrderingOid( orderingOid );
}
else if ( startsWith( pos, SUBSTR_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SUBSTR, pos );
pos.start += SUBSTR_STR.length();
skipWhites( reader, pos, true );
String substrOid = getOidStrict( pos );
attributeType.setSubstringOid( substrOid );
}
else if ( startsWith( pos, SYNTAX_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SYNTAX, pos );
pos.start += SYNTAX_STR.length();
skipWhites( reader, pos, true );
getNoidLenStrict( attributeType, pos );
hasSyntax = true;
}
else if ( startsWith( pos, SINGLE_VALUE_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SINGLE_VALUE, pos );
pos.start += SINGLE_VALUE_STR.length();
attributeType.setSingleValued( true );
}
else if ( startsWith( pos, COLLECTIVE_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.COLLECTIVE, pos );
pos.start += COLLECTIVE_STR.length();
attributeType.setCollective( true );
}
else if ( startsWith( pos, NO_USER_MODIFICATION_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.NO_USER_MODIFICATION, pos );
pos.start += NO_USER_MODIFICATION_STR.length();
attributeType.setUserModifiable( false );
}
else if ( startsWith( pos, USAGE_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.USAGE, pos );
pos.start += USAGE_STR.length();
skipWhites( reader, pos, true );
UsageEnum usage = getUsageStrict( pos );
attributeType.setUsage( usage );
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, attributeType );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13798_AT_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
// Semantic checks
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;
}
/**
* Production for matching attribute type descriptions. It is fault-tolerant
* against element ordering. It's relaxed.
*
* <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>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of AttributeType
* @throws IOException If the stream can't be read
* @throws LdapSchemaException If the schema is wrong
*/
private static AttributeType parseAttributeTypeRelaxed( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the OID.
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
AttributeType attributeType = new AttributeType( oid );
int elementsSeen = 0;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
// Make whitespace non-mandatory here.
// E.g. OpenDJ is missing the the space in some schema definitions.
skipWhites( reader, pos, false );
if ( startsWith( pos, NAME_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.NAME, pos );
pos.start += NAME_STR.length();
skipWhites( reader, pos, true );
attributeType.setNames( getQDescrs( reader, pos, RELAXED ) );
}
else if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
attributeType.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, OBSOLETE_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.OBSOLETE, pos );
pos.start += OBSOLETE_STR.length();
attributeType.setObsolete( true );
}
else if ( startsWith( pos, SUP_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SUP, pos );
pos.start += SUP_STR.length();
skipWhites( reader, pos, true );
String superiorOid = getOidRelaxed( pos, false );
attributeType.setSuperiorOid( superiorOid );
}
else if ( startsWith( pos, EQUALITY_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.EQUALITY, pos );
pos.start += EQUALITY_STR.length();
skipWhites( reader, pos, true );
String equalityOid = getOidRelaxed( pos, false );
attributeType.setEqualityOid( equalityOid );
}
else if ( startsWith( pos, ORDERING_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.ORDERING, pos );
pos.start += ORDERING_STR.length();
skipWhites( reader, pos, true );
String orderingOid = getOidRelaxed( pos, false );
attributeType.setOrderingOid( orderingOid );
}
else if ( startsWith( pos, SUBSTR_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SUBSTR, pos );
pos.start += SUBSTR_STR.length();
skipWhites( reader, pos, true );
String substrOid = getOidRelaxed( pos, false );
attributeType.setSubstringOid( substrOid );
}
else if ( startsWith( pos, SYNTAX_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SYNTAX, pos );
pos.start += SYNTAX_STR.length();
skipWhites( reader, pos, true );
getNoidLenRelaxed( attributeType, pos );
}
else if ( startsWith( pos, SINGLE_VALUE_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SINGLE_VALUE, pos );
pos.start += SINGLE_VALUE_STR.length();
attributeType.setSingleValued( true );
}
else if ( startsWith( pos, COLLECTIVE_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.COLLECTIVE, pos );
pos.start += COLLECTIVE_STR.length();
attributeType.setCollective( true );
}
else if ( startsWith( pos, NO_USER_MODIFICATION_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.NO_USER_MODIFICATION, pos );
pos.start += NO_USER_MODIFICATION_STR.length();
attributeType.setUserModifiable( false );
}
else if ( startsWith( pos, USAGE_STR ) )
{
elementsSeen = checkElement( elementsSeen, AttributeTypeElements.USAGE, pos );
pos.start += USAGE_STR.length();
skipWhites( reader, pos, true );
UsageEnum usage = getUsageRelaxed( pos );
attributeType.setUsage( usage );
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, attributeType );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13798_AT_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
return attributeType;
}
/**
* Production for matching DitContentRule descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* DITContentRuleDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "NAME" SP qdescrs ] ; short names (descriptors)
* [ SP "DESC" SP qdstring ] ; description
* [ SP "OBSOLETE" ] ; not active
* [ SP "AUX" SP oids ] ; auxiliary object classes
* [ SP "MUST" SP oids ] ; attribute types
* [ SP "MAY" SP oids ] ; attribute types
* [ SP "NOT" SP oids ] ; attribute types
* extensions WSP RPAREN ; extensions
* </pre>
*
* @param ditContentRuleDescription The String containing the DitContentRuleDescription
* @return An instance of ditContentRule
* @throws ParseException If the element was invalid
*/
public DitContentRule parseDitContentRule( String ditContentRuleDescription ) throws ParseException
{
if ( ( ditContentRuleDescription == null ) || Strings.isEmpty( ditContentRuleDescription.trim() ) )
{
throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
}
try ( Reader reader = new BufferedReader( new StringReader( ditContentRuleDescription ) ) )
{
PosSchema pos = new PosSchema();
if ( isQuirksModeEnabled )
{
return parseDitContentRuleRelaxed( reader, pos, objectIdentifierMacros );
}
else
{
return parseDitContentRuleStrict( reader, pos, objectIdentifierMacros );
}
}
catch ( IOException | LdapSchemaException e )
{
throw new ParseException( e.getMessage(), 0 );
}
}
/**
* Production for DitContentRule descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* DITContentRuleDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "NAME" SP qdescrs ] ; short names (descriptors)
* [ SP "DESC" SP qdstring ] ; description
* [ SP "OBSOLETE" ] ; not active
* [ SP "AUX" SP oids ] ; auxiliary object classes
* [ SP "MUST" SP oids ] ; attribute types
* [ SP "MAY" SP oids ] ; attribute types
* [ SP "NOT" SP oids ] ; attribute types
* extensions WSP RPAREN ; extensions
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of DitContentRule
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static DitContentRule parseDitContentRuleStrict( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the OID.
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
// Check that the OID is valid
if ( !Oid.isOid( oid ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
}
DitContentRule ditContentRule = new DitContentRule( oid );
int elementsSeen = 0;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, NAME_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitContentRuleElements.NAME, pos );
pos.start += NAME_STR.length();
skipWhites( reader, pos, true );
ditContentRule.setNames( getQDescrs( reader, pos, STRICT ) );
}
else if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitContentRuleElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
ditContentRule.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, OBSOLETE_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitContentRuleElements.OBSOLETE, pos );
pos.start += OBSOLETE_STR.length();
ditContentRule.setObsolete( true );
}
else if ( startsWith( pos, AUX_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitContentRuleElements.AUX, pos );
pos.start += AUX_STR.length();
skipWhites( reader, pos, true );
List<String> aux = getOidsStrict( reader, pos );
ditContentRule.setAuxObjectClassOids( aux );
}
else if ( startsWith( pos, MUST_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitContentRuleElements.MUST, pos );
pos.start += MUST_STR.length();
skipWhites( reader, pos, true );
List<String> must = getOidsStrict( reader, pos );
ditContentRule.setMustAttributeTypeOids( must );
}
else if ( startsWith( pos, MAY_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitContentRuleElements.MAY, pos );
pos.start += MAY_STR.length();
skipWhites( reader, pos, true );
List<String> may = getOidsStrict( reader, pos );
ditContentRule.setMayAttributeTypeOids( may );
}
else if ( startsWith( pos, NOT_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitContentRuleElements.NOT, pos );
pos.start += NOT_STR.length();
skipWhites( reader, pos, true );
List<String> not = getOidsStrict( reader, pos );
ditContentRule.setNotAttributeTypeOids( not );
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, ditContentRule );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13809_DCR_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
return ditContentRule;
}
/**
* Production for DitContentRule descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* DITContentRuleDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "NAME" SP qdescrs ] ; short names (descriptors)
* [ SP "DESC" SP qdstring ] ; description
* [ SP "OBSOLETE" ] ; not active
* [ SP "AUX" SP oids ] ; auxiliary object classes
* [ SP "MUST" SP oids ] ; attribute types
* [ SP "MAY" SP oids ] ; attribute types
* [ SP "NOT" SP oids ] ; attribute types
* extensions WSP RPAREN ; extensions
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of DitContentRule
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static DitContentRule parseDitContentRuleRelaxed( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the OID.
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
// Now, the OID.
DitContentRule ditContentRule = new DitContentRule( oid );
int elementsSeen = 0;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, NAME_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitContentRuleElements.NAME, pos );
pos.start += NAME_STR.length();
skipWhites( reader, pos, true );
ditContentRule.setNames( getQDescrs( reader, pos, RELAXED ) );
}
else if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitContentRuleElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
ditContentRule.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, OBSOLETE_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitContentRuleElements.OBSOLETE, pos );
pos.start += OBSOLETE_STR.length();
ditContentRule.setObsolete( true );
}
else if ( startsWith( pos, AUX_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitContentRuleElements.AUX, pos );
pos.start += AUX_STR.length();
skipWhites( reader, pos, true );
List<String> aux = getOidsRelaxed( reader, pos );
ditContentRule.setAuxObjectClassOids( aux );
}
else if ( startsWith( pos, MUST_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitContentRuleElements.MUST, pos );
pos.start += MUST_STR.length();
skipWhites( reader, pos, true );
List<String> must = getOidsRelaxed( reader, pos );
ditContentRule.setMustAttributeTypeOids( must );
}
else if ( startsWith( pos, MAY_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitContentRuleElements.MAY, pos );
pos.start += MAY_STR.length();
skipWhites( reader, pos, true );
List<String> may = getOidsRelaxed( reader, pos );
ditContentRule.setMayAttributeTypeOids( may );
}
else if ( startsWith( pos, NOT_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitContentRuleElements.NOT, pos );
pos.start += NOT_STR.length();
skipWhites( reader, pos, true );
List<String> not = getOidsRelaxed( reader, pos );
ditContentRule.setNotAttributeTypeOids( not );
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, ditContentRule );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13809_DCR_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
return ditContentRule;
}
/**
* Production for matching DitStructureRule descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* DITStructureRuleDescription = LPAREN WSP
* ruleid ; rule identifier
* [ SP "NAME" SP qdescrs ] ; short names (descriptors)
* [ SP "DESC" SP qdstring ] ; description
* [ SP "OBSOLETE" ] ; not active
* SP "FORM" SP oid ; NameForm
* [ SP "SUP" ruleids ] ; superior rules
* extensions WSP RPAREN ; extensions
*
* ruleids = ruleid / ( LPAREN WSP ruleidlist WSP RPAREN )
* ruleidlist = ruleid *( SP ruleid )
* ruleid = number
* </pre>
*
* @param ditStructureRuleDescription The String containing the DitStructureRuleDescription
* @return An instance of DitStructureRule
* @throws ParseException If the element was invalid
*/
public DitStructureRule parseDitStructureRule( String ditStructureRuleDescription ) throws ParseException
{
if ( ( ditStructureRuleDescription == null ) || Strings.isEmpty( ditStructureRuleDescription.trim() ) )
{
throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
}
try ( Reader reader = new BufferedReader( new StringReader( ditStructureRuleDescription ) ) )
{
PosSchema pos = new PosSchema();
if ( isQuirksModeEnabled )
{
return parseDitStructureRuleRelaxed( reader, pos, objectIdentifierMacros );
}
else
{
return parseDitStructureRuleStrict( reader, pos );
}
}
catch ( IOException | LdapSchemaException e )
{
throw new ParseException( e.getMessage(), 0 );
}
}
/**
* Production for DitStructureRule descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* DITStructureRuleDescription = LPAREN WSP
* ruleid ; rule identifier
* [ SP "NAME" SP qdescrs ] ; short names (descriptors)
* [ SP "DESC" SP qdstring ] ; description
* [ SP "OBSOLETE" ] ; not active
* SP "FORM" SP oid ; NameForm
* [ SP "SUP" ruleids ] ; superior rules
* extensions WSP RPAREN ; extensions
*
* ruleids = ruleid / ( LPAREN WSP ruleidlist WSP RPAREN )
* ruleidlist = ruleid *( SP ruleid )
* ruleid = number
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @return An instance of DitStructureRule
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static DitStructureRule parseDitStructureRuleStrict( 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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the ruleID.
int ruleId = getRuleId( pos );
DitStructureRule ditStructureRule = new DitStructureRule( ruleId );
int elementsSeen = 0;
boolean hasForm = false;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, NAME_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.NAME, pos );
pos.start += NAME_STR.length();
skipWhites( reader, pos, true );
ditStructureRule.setNames( getQDescrs( reader, pos, STRICT ) );
}
else if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
ditStructureRule.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, OBSOLETE_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.OBSOLETE, pos );
pos.start += OBSOLETE_STR.length();
ditStructureRule.setObsolete( true );
}
else if ( startsWith( pos, FORM_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.FORM, pos );
pos.start += FORM_STR.length();
skipWhites( reader, pos, true );
String form = getOidStrict( pos );
ditStructureRule.setForm( form );
hasForm = true;
}
else if ( startsWith( pos, SUP_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.SUP, pos );
pos.start += SUP_STR.length();
skipWhites( reader, pos, true );
List<Integer> superRules = getRuleIds( reader, pos );
ditStructureRule.setSuperRules( superRules );
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, ditStructureRule );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13809_DCR_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
// Semantic checks
if ( !hasForm )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13812_FORM_REQUIRED,
pos.lineNumber, pos.start ) );
}
return ditStructureRule;
}
/**
* Production for DitStructureRule descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* DITStructureRuleDescription = LPAREN WSP
* ruleid ; rule identifier
* [ SP "NAME" SP qdescrs ] ; short names (descriptors)
* [ SP "DESC" SP qdstring ] ; description
* [ SP "OBSOLETE" ] ; not active
* SP "FORM" SP oid ; NameForm
* [ SP "SUP" ruleids ] ; superior rules
* extensions WSP RPAREN ; extensions
*
* ruleids = ruleid / ( LPAREN WSP ruleidlist WSP RPAREN )
* ruleidlist = ruleid *( SP ruleid )
* ruleid = number
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of DitStructureRule
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static DitStructureRule parseDitStructureRuleRelaxed( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros )
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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the ruleID.
int ruleId = getRuleId( pos );
DitStructureRule ditStructureRule = new DitStructureRule( ruleId );
int elementsSeen = 0;
boolean hasForm = false;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, NAME_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.NAME, pos );
pos.start += NAME_STR.length();
skipWhites( reader, pos, true );
ditStructureRule.setNames( getQDescrs( reader, pos, RELAXED ) );
}
else if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
ditStructureRule.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, OBSOLETE_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.OBSOLETE, pos );
pos.start += OBSOLETE_STR.length();
ditStructureRule.setObsolete( true );
}
else if ( startsWith( pos, FORM_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.FORM, pos );
pos.start += FORM_STR.length();
skipWhites( reader, pos, true );
String form = getOidRelaxed( pos, UN_QUOTED );
ditStructureRule.setForm( form );
hasForm = true;
}
else if ( startsWith( pos, SUP_STR ) )
{
elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.SUP, pos );
pos.start += SUP_STR.length();
skipWhites( reader, pos, true );
List<Integer> superRules = getRuleIds( reader, pos );
ditStructureRule.setSuperRules( superRules );
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, ditStructureRule );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13809_DCR_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
if ( !hasForm )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13812_FORM_REQUIRED,
pos.lineNumber, pos.start ) );
}
return ditStructureRule;
}
/**
* Production for LdapComparator descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* LdapComparatorDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "DESC" SP qdstring ] ; description
* SP "FQCN" SP fqcn ; fully qualified class name
* [ SP "BYTECODE" SP base64 ] ; optional base64 encoded bytecode
* extensions WSP RPAREN ; extensions
*
* base64 = *(4base64-char)
* base64-char = ALPHA / DIGIT / "+" / "/"
* fqcn = fqcnComponent 1*( DOT fqcnComponent )
* fqcnComponent = ???
* </pre>
*
* @param ldapComparatorDescription The String containing the LdapComparatorDescription
* @return An instance of LdapComparatorDescription
* @throws ParseException If the element was invalid
*/
public LdapComparatorDescription parseLdapComparator( String ldapComparatorDescription ) throws ParseException
{
if ( ( ldapComparatorDescription == null ) || Strings.isEmpty( ldapComparatorDescription.trim() ) )
{
throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
}
try ( Reader reader = new BufferedReader( new StringReader( ldapComparatorDescription ) ) )
{
PosSchema pos = new PosSchema();
if ( isQuirksModeEnabled )
{
return parseLdapComparatorRelaxed( reader, pos, objectIdentifierMacros );
}
else
{
return parseLdapComparatorStrict( reader, pos, objectIdentifierMacros );
}
}
catch ( IOException | LdapSchemaException e )
{
throw new ParseException( e.getMessage(), 0 );
}
}
/**
* Production for LdapComparator descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* LdapComparatorDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "DESC" SP qdstring ] ; description
* SP "FQCN" SP fqcn ; fully qualified class name
* [ SP "BYTECODE" SP base64 ] ; optional base64 encoded bytecode
* extensions WSP RPAREN ; extensions
*
* base64 = *(4base64-char)
* base64-char = ALPHA / DIGIT / "+" / "/"
* fqcn = fqcnComponent 1*( DOT fqcnComponent )
* fqcnComponent = ???
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of LdapComparatorDescription
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static LdapComparatorDescription parseLdapComparatorStrict( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the OID.
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
// Check that the OID is valid
if ( !Oid.isOid( oid ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
}
LdapComparatorDescription ldapComparator = new LdapComparatorDescription( oid );
int elementsSeen = 0;
boolean hasFqcn = false;
boolean hasByteCode = false;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, LdapComparatorElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
ldapComparator.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, FQCN_STR ) )
{
elementsSeen = checkElement( elementsSeen, LdapComparatorElements.FQCN, pos );
pos.start += FQCN_STR.length();
skipWhites( reader, pos, true );
String fqcn = getFqcn( pos );
ldapComparator.setFqcn( fqcn );
hasFqcn = true;
}
else if ( startsWith( pos, BYTECODE_STR ) )
{
elementsSeen = checkElement( elementsSeen, LdapComparatorElements.BYTECODE, pos );
pos.start += BYTECODE_STR.length();
skipWhites( reader, pos, true );
String byteCode = getByteCode( pos );
ldapComparator.setBytecode( byteCode );
hasByteCode = true;
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, ldapComparator );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13825_COMP_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
// Semantic checks
if ( !hasFqcn )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13819_FQCN_REQUIRED,
pos.lineNumber, pos.start ) );
}
if ( ( hasByteCode ) && ( ldapComparator.getBytecode().length() % 4 != 0 ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13820_BYTE_CODE_REQUIRED,
pos.lineNumber, pos.start ) );
}
return ldapComparator;
}
/**
* Production for LdapComparator descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* LdapComparatorDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "DESC" SP qdstring ] ; description
* SP "FQCN" SP fqcn ; fully qualified class name
* [ SP "BYTECODE" SP base64 ] ; optional base64 encoded bytecode
* extensions WSP RPAREN ; extensions
*
* base64 = *(4base64-char)
* base64-char = ALPHA / DIGIT / "+" / "/"
* fqcn = fqcnComponent 1*( DOT fqcnComponent )
* fqcnComponent = ???
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of LdapComparatorDescription
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static LdapComparatorDescription parseLdapComparatorRelaxed( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros )
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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the OID.
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
LdapComparatorDescription ldapComparator = new LdapComparatorDescription( oid );
int elementsSeen = 0;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, LdapComparatorElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
ldapComparator.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, FQCN_STR ) )
{
elementsSeen = checkElement( elementsSeen, LdapComparatorElements.FQCN, pos );
pos.start += FQCN_STR.length();
skipWhites( reader, pos, true );
String fqcn = getFqcn( pos );
ldapComparator.setFqcn( fqcn );
}
else if ( startsWith( pos, BYTECODE_STR ) )
{
elementsSeen = checkElement( elementsSeen, LdapComparatorElements.BYTECODE, pos );
pos.start += BYTECODE_STR.length();
skipWhites( reader, pos, true );
String byteCode = getByteCode( pos );
ldapComparator.setBytecode( byteCode );
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, ldapComparator );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13825_COMP_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
return ldapComparator;
}
/**
* Production for matching ldap syntax descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* SyntaxDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "DESC" SP qdstring ] ; description
* extensions WSP RPAREN ; extensions
* </pre>
*
* @param ldapSyntaxDescription The String containing the Ldap Syntax description
* @return An instance of LdapSyntax
* @throws ParseException If the element was invalid
*/
public LdapSyntax parseLdapSyntax( String ldapSyntaxDescription ) throws ParseException
{
if ( ( ldapSyntaxDescription == null ) || Strings.isEmpty( ldapSyntaxDescription.trim() ) )
{
throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
}
try ( Reader reader = new BufferedReader( new StringReader( ldapSyntaxDescription ) ) )
{
PosSchema pos = new PosSchema();
if ( isQuirksModeEnabled )
{
return parseLdapSyntaxRelaxed( reader, pos, objectIdentifierMacros );
}
else
{
return parseLdapSyntaxStrict( reader, pos, objectIdentifierMacros );
}
}
catch ( IOException | LdapSchemaException e )
{
throw new ParseException( e.getMessage(), 0 );
}
}
/**
* Production for matching ldap syntax descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* SyntaxDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "DESC" SP qdstring ] ; description
* extensions WSP RPAREN ; extensions
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of LdapSyntax
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static LdapSyntax parseLdapSyntaxStrict( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the OID.
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
// Check that the OID is valid
if ( !Oid.isOid( oid ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
}
LdapSyntax ldapSyntax = new LdapSyntax( oid );
int elementsSeen = 0;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, LdapSyntaxElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
ldapSyntax.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, ldapSyntax );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13807_SYN_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
return ldapSyntax;
}
/**
* Production for matching ldap syntax descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* SyntaxDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "DESC" SP qdstring ] ; description
* extensions WSP RPAREN ; extensions
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of LdapSyntax
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static LdapSyntax parseLdapSyntaxRelaxed( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the OID.
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
LdapSyntax ldapSyntax = new LdapSyntax( oid );
int elementsSeen = 0;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, LdapSyntaxElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
ldapSyntax.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, ldapSyntax );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13807_SYN_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
return ldapSyntax;
}
/**
* Production for matching MatchingRule descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* MatchingRuleDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "NAME" SP qdescrs ] ; short names (descriptors)
* [ SP "DESC" SP qdstring ] ; description
* [ SP "OBSOLETE" ] ; not active
* SP "SYNTAX" SP numericoid ; assertion syntax
* extensions WSP RPAREN ; extensions
* </pre>
*
* @param matchingRuleDescription The String containing the MatchingRuledescription
* @return An instance of MatchingRule
* @throws ParseException If the element was invalid
*/
public MatchingRule parseMatchingRule( String matchingRuleDescription ) throws ParseException
{
if ( ( matchingRuleDescription == null ) || Strings.isEmpty( matchingRuleDescription.trim() ) )
{
throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
}
try ( Reader reader = new BufferedReader( new StringReader( matchingRuleDescription ) ) )
{
PosSchema pos = new PosSchema();
if ( isQuirksModeEnabled )
{
return parseMatchingRuleRelaxed( reader, pos, objectIdentifierMacros );
}
else
{
return parseMatchingRuleStrict( reader, pos, objectIdentifierMacros );
}
}
catch ( IOException | LdapSchemaException e )
{
throw new ParseException( e.getMessage(), 0 );
}
}
/**
* Production for matching rule descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* MatchingRuleDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "NAME" SP qdescrs ] ; short names (descriptors)
* [ SP "DESC" SP qdstring ] ; description
* [ SP "OBSOLETE" ] ; not active
* SP "SYNTAX" SP numericoid ; assertion syntax
* extensions WSP RPAREN ; extensions
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of MatchingRule
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static MatchingRule parseMatchingRuleStrict( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the OID.
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
// Check that the OID is valid
if ( !Oid.isOid( oid ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
}
MatchingRule matchingRule = new MatchingRule( oid );
int elementsSeen = 0;
boolean hasSyntax = false;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, NAME_STR ) )
{
elementsSeen = checkElement( elementsSeen, MatchingRuleElements.NAME, pos );
pos.start += NAME_STR.length();
skipWhites( reader, pos, true );
matchingRule.setNames( getQDescrs( reader, pos, STRICT ) );
}
else if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, MatchingRuleElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
matchingRule.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, OBSOLETE_STR ) )
{
elementsSeen = checkElement( elementsSeen, MatchingRuleElements.OBSOLETE, pos );
pos.start += OBSOLETE_STR.length();
matchingRule.setObsolete( true );
}
else if ( startsWith( pos, SYNTAX_STR ) )
{
elementsSeen = checkElement( elementsSeen, MatchingRuleElements.SYNTAX, pos );
pos.start += SYNTAX_STR.length();
skipWhites( reader, pos, true );
String syntaxOid = getNumericOid( pos );
matchingRule.setSyntaxOid( syntaxOid );
hasSyntax = true;
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, matchingRule );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13781_MR_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
// Semantic checks
if ( !hasSyntax )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13808_SYNTAX_REQUIRED,
pos.lineNumber, pos.start ) );
}
return matchingRule;
}
/**
* Production for matching rule descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* MatchingRuleDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "NAME" SP qdescrs ] ; short names (descriptors)
* [ SP "DESC" SP qdstring ] ; description
* [ SP "OBSOLETE" ] ; not active
* SP "SYNTAX" SP numericoid ; assertion syntax
* extensions WSP RPAREN ; extensions
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of MatchingRule
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static MatchingRule parseMatchingRuleRelaxed( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros )
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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the OID.
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
MatchingRule matchingRule = new MatchingRule( oid );
int elementsSeen = 0;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, NAME_STR ) )
{
elementsSeen = checkElement( elementsSeen, MatchingRuleElements.NAME, pos );
pos.start += NAME_STR.length();
skipWhites( reader, pos, true );
matchingRule.setNames( getQDescrs( reader, pos, RELAXED ) );
}
else if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, MatchingRuleElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
matchingRule.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, OBSOLETE_STR ) )
{
elementsSeen = checkElement( elementsSeen, MatchingRuleElements.OBSOLETE, pos );
pos.start += OBSOLETE_STR.length();
matchingRule.setObsolete( true );
}
else if ( startsWith( pos, SYNTAX_STR ) )
{
elementsSeen = checkElement( elementsSeen, MatchingRuleElements.SYNTAX, pos );
pos.start += SYNTAX_STR.length();
skipWhites( reader, pos, true );
String syntaxOid = getNumericOid( pos );
matchingRule.setSyntaxOid( syntaxOid );
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, matchingRule );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13781_MR_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
return matchingRule;
}
/**
* Production for matching MatchingRuleUse descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* MatchingRuleUseDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "NAME" SP qdescrs ] ; short names (descriptors)
* [ SP "DESC" SP qdstring ] ; description
* [ SP "OBSOLETE" ] ; not active
* SP "APPLIES" SP oids ; attribute types
* extensions WSP RPAREN ; extensions
* </pre>
*
* @param matchingRuleUseDescription The String containing the MatchingRuleUsedescription
* @return An instance of MatchingRuleUse
* @throws ParseException If the element was invalid
*/
public MatchingRuleUse parseMatchingRuleUse( String matchingRuleUseDescription ) throws ParseException
{
if ( ( matchingRuleUseDescription == null ) || Strings.isEmpty( matchingRuleUseDescription.trim() ) )
{
throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
}
try ( Reader reader = new BufferedReader( new StringReader( matchingRuleUseDescription ) ) )
{
PosSchema pos = new PosSchema();
if ( isQuirksModeEnabled )
{
return parseMatchingRuleUseRelaxed( reader, pos, objectIdentifierMacros );
}
else
{
return parseMatchingRuleUseStrict( reader, pos, objectIdentifierMacros );
}
}
catch ( IOException | LdapSchemaException e )
{
throw new ParseException( e.getMessage(), 0 );
}
}
/**
* Production for MatchingRuleUse descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* MatchingRuleUseDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "NAME" SP qdescrs ] ; short names (descriptors)
* [ SP "DESC" SP qdstring ] ; description
* [ SP "OBSOLETE" ] ; not active
* SP "APPLIES" SP oids ; attribute types
* extensions WSP RPAREN ; extensions
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of MatchingRuleUse
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static MatchingRuleUse parseMatchingRuleUseStrict( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the OID.
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
// Check that the OID is valid
if ( !Oid.isOid( oid ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
}
MatchingRuleUse matchingRuleUse = new MatchingRuleUse( oid );
int elementsSeen = 0;
boolean hasApplies = false;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, false );
if ( startsWith( pos, NAME_STR ) )
{
elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.NAME, pos );
pos.start += NAME_STR.length();
skipWhites( reader, pos, true );
matchingRuleUse.setNames( getQDescrs( reader, pos, STRICT ) );
}
else if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
matchingRuleUse.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, OBSOLETE_STR ) )
{
elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.OBSOLETE, pos );
pos.start += OBSOLETE_STR.length();
matchingRuleUse.setObsolete( true );
}
else if ( startsWith( pos, APPLIES_STR ) )
{
elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.APPLIES, pos );
pos.start += APPLIES_STR.length();
skipWhites( reader, pos, true );
List<String> oids = getOidsStrict( reader, pos );
matchingRuleUse.setApplicableAttributeOids( oids );
hasApplies = true;
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, matchingRuleUse );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13815_MRU_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
// Semantic checks
if ( !hasApplies )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13814_APPLIES_REQUIRED,
pos.lineNumber, pos.start ) );
}
return matchingRuleUse;
}
/**
* Production for MatchingRuleUse descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* MatchingRuleUseDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "NAME" SP qdescrs ] ; short names (descriptors)
* [ SP "DESC" SP qdstring ] ; description
* [ SP "OBSOLETE" ] ; not active
* SP "APPLIES" SP oids ; attribute types
* extensions WSP RPAREN ; extensions
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of MatchingRuleUse
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static MatchingRuleUse parseMatchingRuleUseRelaxed( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros )
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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the OID.
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
MatchingRuleUse matchingRuleUse = new MatchingRuleUse( oid );
int elementsSeen = 0;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, NAME_STR ) )
{
elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.NAME, pos );
pos.start += NAME_STR.length();
skipWhites( reader, pos, true );
matchingRuleUse.setNames( getQDescrs( reader, pos, RELAXED ) );
}
else if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
matchingRuleUse.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, OBSOLETE_STR ) )
{
elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.OBSOLETE, pos );
pos.start += OBSOLETE_STR.length();
matchingRuleUse.setObsolete( true );
}
else if ( startsWith( pos, APPLIES_STR ) )
{
elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.APPLIES, pos );
pos.start += APPLIES_STR.length();
skipWhites( reader, pos, true );
List<String> oids = getOidsRelaxed( reader, pos );
matchingRuleUse.setApplicableAttributeOids( oids );
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, matchingRuleUse );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13815_MRU_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
return matchingRuleUse;
}
/**
* Production for NameForm descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* NameFormDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "NAME" SP qdescrs ] ; short names (descriptors)
* [ SP "DESC" SP qdstring ] ; description
* [ SP "OBSOLETE" ] ; not active
* SP "OC" SP oid ; structural object class
* SP "MUST" SP oids ; attribute types
* [ SP "MAY" SP oids ] ; attribute types
* extensions WSP RPAREN ; extensions
* </pre>
*
* @param nameFormDescription The String containing the NameFormdescription
* @return An instance of NameForm
* @throws ParseException If the element was invalid
*/
public NameForm parseNameForm( String nameFormDescription ) throws ParseException
{
if ( ( nameFormDescription == null ) || Strings.isEmpty( nameFormDescription.trim() ) )
{
throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
}
try ( Reader reader = new BufferedReader( new StringReader( nameFormDescription ) ) )
{
PosSchema pos = new PosSchema();
if ( isQuirksModeEnabled )
{
return parseNameFormRelaxed( reader, pos, objectIdentifierMacros );
}
else
{
return parseNameFormStrict( reader, pos, objectIdentifierMacros );
}
}
catch ( IOException | LdapSchemaException e )
{
throw new ParseException( e.getMessage(), 0 );
}
}
/**
* Production for NameForm descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* NameFormDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "NAME" SP qdescrs ] ; short names (descriptors)
* [ SP "DESC" SP qdstring ] ; description
* [ SP "OBSOLETE" ] ; not active
* SP "OC" SP oid ; structural object class
* SP "MUST" SP oids ; attribute types
* [ SP "MAY" SP oids ] ; attribute types
* extensions WSP RPAREN ; extensions
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @return An instance of NameForm
* @param objectIdentifierMacros The set of existing Macros
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static NameForm parseNameFormStrict( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the OID.
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
// Check that the OID is valid
if ( !Oid.isOid( oid ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
}
NameForm nameForm = new NameForm( oid );
int elementsSeen = 0;
boolean hasOc = false;
boolean hasMust = false;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, NAME_STR ) )
{
elementsSeen = checkElement( elementsSeen, NameFormElements.NAME, pos );
pos.start += NAME_STR.length();
skipWhites( reader, pos, true );
nameForm.setNames( getQDescrs( reader, pos, STRICT ) );
}
else if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, NameFormElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
nameForm.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, OBSOLETE_STR ) )
{
elementsSeen = checkElement( elementsSeen, NameFormElements.OBSOLETE, pos );
pos.start += OBSOLETE_STR.length();
nameForm.setObsolete( true );
}
else if ( startsWith( pos, OC_STR ) )
{
elementsSeen = checkElement( elementsSeen, NameFormElements.OC, pos );
pos.start += OC_STR.length();
skipWhites( reader, pos, true );
String oc = getOidStrict( pos );
nameForm.setStructuralObjectClassOid( oc );
hasOc = true;
}
else if ( startsWith( pos, MUST_STR ) )
{
elementsSeen = checkElement( elementsSeen, NameFormElements.MUST, pos );
pos.start += MUST_STR.length();
skipWhites( reader, pos, true );
List<String> must = getOidsStrict( reader, pos );
nameForm.setMustAttributeTypeOids( must );
hasMust = true;
}
else if ( startsWith( pos, MAY_STR ) )
{
elementsSeen = checkElement( elementsSeen, NameFormElements.MAY, pos );
pos.start += MAY_STR.length();
skipWhites( reader, pos, true );
List<String> may = getOidsStrict( reader, pos );
nameForm.setMayAttributeTypeOids( may );
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, nameForm );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13816_NF_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
// Semantic checks
if ( !hasOc )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13817_STRUCTURAL_OBJECT_CLASS_REQUIRED,
pos.lineNumber, pos.start ) );
}
if ( !hasMust )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13818_MUST_REQUIRED,
pos.lineNumber, pos.start ) );
}
return nameForm;
}
/**
* Production for NameForm descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* NameFormDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "NAME" SP qdescrs ] ; short names (descriptors)
* [ SP "DESC" SP qdstring ] ; description
* [ SP "OBSOLETE" ] ; not active
* SP "OC" SP oid ; structural object class
* SP "MUST" SP oids ; attribute types
* [ SP "MAY" SP oids ] ; attribute types
* extensions WSP RPAREN ; extensions
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of NameForm
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static NameForm parseNameFormRelaxed( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros )
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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the OID.
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
NameForm nameForm = new NameForm( oid );
int elementsSeen = 0;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, NAME_STR ) )
{
elementsSeen = checkElement( elementsSeen, NameFormElements.NAME, pos );
pos.start += NAME_STR.length();
skipWhites( reader, pos, true );
nameForm.setNames( getQDescrs( reader, pos, RELAXED ) );
}
else if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, NameFormElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
nameForm.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, OBSOLETE_STR ) )
{
elementsSeen = checkElement( elementsSeen, NameFormElements.OBSOLETE, pos );
pos.start += OBSOLETE_STR.length();
nameForm.setObsolete( true );
}
else if ( startsWith( pos, OC_STR ) )
{
elementsSeen = checkElement( elementsSeen, NameFormElements.OC, pos );
pos.start += OC_STR.length();
skipWhites( reader, pos, true );
String oc = getOidRelaxed( pos, UN_QUOTED );
nameForm.setStructuralObjectClassOid( oc );
}
else if ( startsWith( pos, MUST_STR ) )
{
elementsSeen = checkElement( elementsSeen, NameFormElements.MUST, pos );
pos.start += MUST_STR.length();
skipWhites( reader, pos, true );
List<String> must = getOidsRelaxed( reader, pos );
nameForm.setMustAttributeTypeOids( must );
}
else if ( startsWith( pos, MAY_STR ) )
{
elementsSeen = checkElement( elementsSeen, NameFormElements.MAY, pos );
pos.start += MAY_STR.length();
skipWhites( reader, pos, true );
List<String> may = getOidsRelaxed( reader, pos );
nameForm.setMayAttributeTypeOids( may );
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, nameForm );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13816_NF_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
return nameForm;
}
/**
* Production for Normalizer descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* NormalizerDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "DESC" SP qdstring ] ; description
* SP "FQCN" SP fqcn ; fully qualified class name
* [ SP "BYTECODE" SP base64 ] ; optional base64 encoded bytecode
* extensions WSP RPAREN ; extensions
*
* base64 = *(4base64-char)
* base64-char = ALPHA / DIGIT / "+" / "/"
* fqcn = fqcnComponent 1*( DOT fqcnComponent )
* fqcnComponent = ???
* </pre>
*
* @param normalizerDescription The String containing the NormalizerDescription
* @return An instance of NormalizerDescription
* @throws ParseException If the element was invalid
*/
public NormalizerDescription parseNormalizer( String normalizerDescription ) throws ParseException
{
if ( ( normalizerDescription == null ) || Strings.isEmpty( normalizerDescription.trim() ) )
{
throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
}
try ( Reader reader = new BufferedReader( new StringReader( normalizerDescription ) ) )
{
PosSchema pos = new PosSchema();
if ( isQuirksModeEnabled )
{
return parseNormalizerRelaxed( reader, pos, objectIdentifierMacros );
}
else
{
return parseNormalizerStrict( reader, pos, objectIdentifierMacros );
}
}
catch ( IOException | LdapSchemaException e )
{
throw new ParseException( e.getMessage(), 0 );
}
}
/**
* Production for Normalizer descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* NormalizerDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "DESC" SP qdstring ] ; description
* SP "FQCN" SP fqcn ; fully qualified class name
* [ SP "BYTECODE" SP base64 ] ; optional base64 encoded bytecode
* extensions WSP RPAREN ; extensions
*
* base64 = *(4base64-char)
* base64-char = ALPHA / DIGIT / "+" / "/"
* fqcn = fqcnComponent 1*( DOT fqcnComponent )
* fqcnComponent = ???
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of NormalizerDescription
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static NormalizerDescription parseNormalizerStrict( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the OID.
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
// Check that the OID is valid
if ( !Oid.isOid( oid ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
}
NormalizerDescription normalizer = new NormalizerDescription( oid );
int elementsSeen = 0;
boolean hasFqcn = false;
boolean hasByteCode = false;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, NormalizerElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
normalizer.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, FQCN_STR ) )
{
elementsSeen = checkElement( elementsSeen, NormalizerElements.FQCN, pos );
pos.start += FQCN_STR.length();
skipWhites( reader, pos, true );
String fqcn = getFqcn( pos );
normalizer.setFqcn( fqcn );
hasFqcn = true;
}
else if ( startsWith( pos, BYTECODE_STR ) )
{
elementsSeen = checkElement( elementsSeen, NormalizerElements.BYTECODE, pos );
pos.start += BYTECODE_STR.length();
skipWhites( reader, pos, true );
String byteCode = getByteCode( pos );
normalizer.setBytecode( byteCode );
hasByteCode = true;
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, normalizer );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13821_NORM_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
// Semantic checks
if ( !hasFqcn )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13819_FQCN_REQUIRED,
pos.lineNumber, pos.start ) );
}
if ( ( hasByteCode ) && ( normalizer.getBytecode().length() % 4 != 0 ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13820_BYTE_CODE_REQUIRED,
pos.lineNumber, pos.start ) );
}
return normalizer;
}
/**
* Production for Normalizer descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* NormalizerDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "DESC" SP qdstring ] ; description
* SP "FQCN" SP fqcn ; fully qualified class name
* [ SP "BYTECODE" SP base64 ] ; optional base64 encoded bytecode
* extensions WSP RPAREN ; extensions
*
* base64 = *(4base64-char)
* base64-char = ALPHA / DIGIT / "+" / "/"
* fqcn = fqcnComponent 1*( DOT fqcnComponent )
* fqcnComponent = ???
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of NormalizerDescription
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static NormalizerDescription parseNormalizerRelaxed( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros )
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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the OID.
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
NormalizerDescription normalizer = new NormalizerDescription( oid );
int elementsSeen = 0;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, NormalizerElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
normalizer.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, FQCN_STR ) )
{
elementsSeen = checkElement( elementsSeen, NormalizerElements.FQCN, pos );
pos.start += FQCN_STR.length();
skipWhites( reader, pos, true );
String fqcn = getFqcn( pos );
normalizer.setFqcn( fqcn );
}
else if ( startsWith( pos, BYTECODE_STR ) )
{
elementsSeen = checkElement( elementsSeen, NormalizerElements.BYTECODE, pos );
pos.start += BYTECODE_STR.length();
skipWhites( reader, pos, true );
String byteCode = getByteCode( pos );
normalizer.setBytecode( byteCode );
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, normalizer );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13821_NORM_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
return normalizer;
}
/**
* 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>
*
* @param objectClassDescription The String containing the ObjectClassDescription
* @return An instance of objectClass
* @throws ParseException If the element was invalid
*/
public ObjectClass parseObjectClass( String objectClassDescription ) throws ParseException
{
if ( ( objectClassDescription == null ) || Strings.isEmpty( objectClassDescription.trim() ) )
{
throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
}
try ( Reader reader = new BufferedReader( new StringReader( objectClassDescription ) ) )
{
PosSchema pos = new PosSchema();
if ( isQuirksModeEnabled )
{
return parseObjectClassRelaxed( reader, pos, objectIdentifierMacros );
}
else
{
return parseObjectClassStrict( reader, pos, objectIdentifierMacros );
}
}
catch ( IOException | LdapSchemaException e )
{
throw new ParseException( e.getMessage(), 0 );
}
}
/**
* 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>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of ObjectClass
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static ObjectClass parseObjectClassStrict( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros )
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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the numeric OID
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
// Check that the OID is valid
if ( !Oid.isOid( oid ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
}
ObjectClass objectClass = new ObjectClass( oid );
int elementsSeen = 0;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, NAME_STR ) )
{
elementsSeen = checkElement( elementsSeen, ObjectClassElements.NAME, pos );
pos.start += NAME_STR.length();
skipWhites( reader, pos, true );
List<String> names = getQDescrs( reader, pos, STRICT );
objectClass.setNames( names );
}
else if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, ObjectClassElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
objectClass.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, OBSOLETE_STR ) )
{
elementsSeen = checkElement( elementsSeen, ObjectClassElements.OBSOLETE, pos );
pos.start += OBSOLETE_STR.length();
objectClass.setObsolete( true );
}
else if ( startsWith( pos, SUP_STR ) )
{
elementsSeen = checkElement( elementsSeen, ObjectClassElements.SUP, pos );
pos.start += SUP_STR.length();
skipWhites( reader, pos, true );
List<String> superiorOids = getOidsStrict( reader, pos );
objectClass.setSuperiorOids( superiorOids );
}
else if ( startsWith( pos, ABSTRACT_STR ) )
{
elementsSeen = checkElement( elementsSeen, ObjectClassElements.ABSTRACT, pos );
pos.start += ABSTRACT_STR.length();
objectClass.setType( ObjectClassTypeEnum.ABSTRACT );
}
else if ( startsWith( pos, STRUCTURAL_STR ) )
{
elementsSeen = checkElement( elementsSeen, ObjectClassElements.STRUCTURAL, pos );
pos.start += STRUCTURAL_STR.length();
objectClass.setType( ObjectClassTypeEnum.STRUCTURAL );
}
else if ( startsWith( pos, AUXILIARY_STR ) )
{
elementsSeen = checkElement( elementsSeen, ObjectClassElements.AUXILIARY, pos );
pos.start += AUXILIARY_STR.length();
objectClass.setType( ObjectClassTypeEnum.AUXILIARY );
}
else if ( startsWith( pos, MUST_STR ) )
{
elementsSeen = checkElement( elementsSeen, ObjectClassElements.MUST, pos );
pos.start += MUST_STR.length();
skipWhites( reader, pos, true );
List<String> mustAttributeTypes = getOidsStrict( reader, pos );
objectClass.setMustAttributeTypeOids( mustAttributeTypes );
}
else if ( startsWith( pos, MAY_STR ) )
{
elementsSeen = checkElement( elementsSeen, ObjectClassElements.MAY, pos );
pos.start += MAY_STR.length();
skipWhites( reader, pos, true );
List<String> mayAttributeTypes = getOidsStrict( reader, pos );
objectClass.setMayAttributeTypeOids( mayAttributeTypes );
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, objectClass );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13803_OC_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
pos.start++;
return objectClass;
}
/**
* 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>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of ObjectClass
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static ObjectClass parseObjectClassRelaxed( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros )
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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the numeric OID
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
ObjectClass objectClass = new ObjectClass( oid );
int elementsSeen = 0;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, NAME_STR ) )
{
elementsSeen = checkElement( elementsSeen, ObjectClassElements.NAME, pos );
pos.start += NAME_STR.length();
skipWhites( reader, pos, true );
List<String> names = getQDescrs( reader, pos, RELAXED );
objectClass.setNames( names );
}
else if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, ObjectClassElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
objectClass.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, OBSOLETE_STR ) )
{
elementsSeen = checkElement( elementsSeen, ObjectClassElements.OBSOLETE, pos );
pos.start += OBSOLETE_STR.length();
objectClass.setObsolete( true );
}
else if ( startsWith( pos, SUP_STR ) )
{
elementsSeen = checkElement( elementsSeen, ObjectClassElements.SUP, pos );
pos.start += SUP_STR.length();
skipWhites( reader, pos, true );
List<String> superiorOids = getOidsRelaxed( reader, pos );
objectClass.setSuperiorOids( superiorOids );
}
else if ( startsWith( pos, ABSTRACT_STR ) )
{
elementsSeen = checkElement( elementsSeen, ObjectClassElements.ABSTRACT, pos );
pos.start += ABSTRACT_STR.length();
objectClass.setType( ObjectClassTypeEnum.ABSTRACT );
}
else if ( startsWith( pos, STRUCTURAL_STR ) )
{
elementsSeen = checkElement( elementsSeen, ObjectClassElements.STRUCTURAL, pos );
pos.start += STRUCTURAL_STR.length();
objectClass.setType( ObjectClassTypeEnum.STRUCTURAL );
}
else if ( startsWith( pos, AUXILIARY_STR ) )
{
elementsSeen = checkElement( elementsSeen, ObjectClassElements.AUXILIARY, pos );
pos.start += AUXILIARY_STR.length();
objectClass.setType( ObjectClassTypeEnum.AUXILIARY );
}
else if ( startsWith( pos, MUST_STR ) )
{
elementsSeen = checkElement( elementsSeen, ObjectClassElements.MUST, pos );
pos.start += MUST_STR.length();
skipWhites( reader, pos, true );
List<String> mustAttributeTypes = getOidsRelaxed( reader, pos );
objectClass.setMustAttributeTypeOids( mustAttributeTypes );
}
else if ( startsWith( pos, MAY_STR ) )
{
elementsSeen = checkElement( elementsSeen, ObjectClassElements.MAY, pos );
pos.start += MAY_STR.length();
skipWhites( reader, pos, true );
List<String> mayAttributeTypes = getOidsRelaxed( reader, pos );
objectClass.setMayAttributeTypeOids( mayAttributeTypes );
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, objectClass );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13803_OC_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
pos.start++;
return objectClass;
}
/**
* Production for SyntaxChecker descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* SyntaxCheckerDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "DESC" SP qdstring ] ; description
* SP "FQCN" SP fqcn ; fully qualified class name
* [ SP "BYTECODE" SP base64 ] ; optional base64 encoded bytecode
* extensions WSP RPAREN ; extensions
*
* base64 = *(4base64-char)
* base64-char = ALPHA / DIGIT / "+" / "/"
* fqcn = fqcnComponent 1*( DOT fqcnComponent )
* fqcnComponent = ???
* </pre>
*
* @param syntaxCheckerDescription The String containing the SyntaxCheckerDescription
* @return An instance of SyntaxCheckerDescription
* @throws ParseException If the element was invalid
*/
public SyntaxCheckerDescription parseSyntaxChecker( String syntaxCheckerDescription ) throws ParseException
{
if ( ( syntaxCheckerDescription == null ) || Strings.isEmpty( syntaxCheckerDescription.trim() ) )
{
throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 );
}
try ( Reader reader = new BufferedReader( new StringReader( syntaxCheckerDescription ) ) )
{
PosSchema pos = new PosSchema();
if ( isQuirksModeEnabled )
{
return parseSyntaxCheckerRelaxed( reader, pos, objectIdentifierMacros );
}
else
{
return parseSyntaxCheckerStrict( reader, pos, objectIdentifierMacros );
}
}
catch ( IOException | LdapSchemaException e )
{
throw new ParseException( e.getMessage(), 0 );
}
}
/**
* Production for SyntaxChecker descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* SyntaxCheckerDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "DESC" SP qdstring ] ; description
* SP "FQCN" SP fqcn ; fully qualified class name
* [ SP "BYTECODE" SP base64 ] ; optional base64 encoded bytecode
* extensions WSP RPAREN ; extensions
*
* base64 = *(4base64-char)
* base64-char = ALPHA / DIGIT / "+" / "/"
* fqcn = fqcnComponent 1*( DOT fqcnComponent )
* fqcnComponent = ???
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of SyntaxCheckerDescription
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static SyntaxCheckerDescription parseSyntaxCheckerStrict( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the OID.
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
// Check that the OID is valid
if ( !Oid.isOid( oid ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) );
}
SyntaxCheckerDescription syntaxChecker = new SyntaxCheckerDescription( oid );
int elementsSeen = 0;
boolean hasFqcn = false;
boolean hasByteCode = false;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, SyntaxCheckerElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
syntaxChecker.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, FQCN_STR ) )
{
elementsSeen = checkElement( elementsSeen, SyntaxCheckerElements.FQCN, pos );
pos.start += FQCN_STR.length();
skipWhites( reader, pos, true );
String fqcn = getFqcn( pos );
syntaxChecker.setFqcn( fqcn );
hasFqcn = true;
}
else if ( startsWith( pos, BYTECODE_STR ) )
{
elementsSeen = checkElement( elementsSeen, SyntaxCheckerElements.BYTECODE, pos );
pos.start += BYTECODE_STR.length();
skipWhites( reader, pos, true );
String byteCode = getByteCode( pos );
syntaxChecker.setBytecode( byteCode );
hasByteCode = true;
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, syntaxChecker );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13826_SC_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
// Semantic checks
if ( !hasFqcn )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13819_FQCN_REQUIRED,
pos.lineNumber, pos.start ) );
}
if ( ( hasByteCode ) && ( syntaxChecker.getBytecode().length() % 4 != 0 ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13820_BYTE_CODE_REQUIRED,
pos.lineNumber, pos.start ) );
}
return syntaxChecker;
}
/**
* Production for SyntaxChecker descriptions. It is fault-tolerant
* against element ordering.
*
* <pre>
* SyntaxCheckerDescription = LPAREN WSP
* numericoid ; object identifier
* [ SP "DESC" SP qdstring ] ; description
* SP "FQCN" SP fqcn ; fully qualified class name
* [ SP "BYTECODE" SP base64 ] ; optional base64 encoded bytecode
* extensions WSP RPAREN ; extensions
*
* base64 = *(4base64-char)
* base64-char = ALPHA / DIGIT / "+" / "/"
* fqcn = fqcnComponent 1*( DOT fqcnComponent )
* fqcnComponent = ???
* </pre>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @param objectIdentifierMacros The set of existing Macros
* @return An instance of SyntaxCheckerDescription
* @throws LdapSchemaException If the schema is wrong
* @throws IOException If the stream can't be read
*/
private static SyntaxCheckerDescription parseSyntaxCheckerRelaxed( Reader reader, PosSchema pos,
Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros )
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 ) != LPAREN )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN,
pos.lineNumber, pos.start ) );
}
else
{
pos.start++;
}
// Get rid of whites, comments end empty lines
skipWhites( reader, pos, false );
// Now, the OID.
String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros );
SyntaxCheckerDescription syntaxChecker = new SyntaxCheckerDescription( oid );
int elementsSeen = 0;
while ( true )
{
if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
skipWhites( reader, pos, true );
if ( startsWith( pos, DESC_STR ) )
{
elementsSeen = checkElement( elementsSeen, SyntaxCheckerElements.DESC, pos );
pos.start += DESC_STR.length();
skipWhites( reader, pos, true );
syntaxChecker.setDescription( getQDString( reader, pos ) );
}
else if ( startsWith( pos, FQCN_STR ) )
{
elementsSeen = checkElement( elementsSeen, SyntaxCheckerElements.FQCN, pos );
pos.start += FQCN_STR.length();
skipWhites( reader, pos, true );
String fqcn = getFqcn( pos );
syntaxChecker.setFqcn( fqcn );
}
else if ( startsWith( pos, BYTECODE_STR ) )
{
elementsSeen = checkElement( elementsSeen, SyntaxCheckerElements.BYTECODE, pos );
pos.start += BYTECODE_STR.length();
skipWhites( reader, pos, true );
String byteCode = getByteCode( pos );
syntaxChecker.setBytecode( byteCode );
}
else if ( startsWith( pos, EXTENSION_PREFIX ) )
{
processExtension( reader, pos, syntaxChecker );
}
else if ( startsWith( reader, pos, RPAREN ) )
{
pos.start++;
break;
}
else
{
// This is an error
throw new LdapSchemaException( I18n.err( I18n.ERR_13826_SC_DESCRIPTION_INVALID,
pos.lineNumber, pos.start ) );
}
}
return syntaxChecker;
}
/**
* 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>
*
* @param reader The stream reader
* @param pos The position in the Schema
* @throws LdapSchemaException If something went wrong in the schema
* @throws IOException If the stream can't be read
*/
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 = getDescrStrict( pos );
OpenLdapObjectIdentifierMacro macro = new OpenLdapObjectIdentifierMacro();
skipWhites( reader, pos, true );
// Get the descr, if any
if ( isEmpty( pos ) )
{
throw new LdapSchemaException( I18n.err( I18n.ERR_13805_OBJECT_IDENTIFIER_INVALID_OID,
pos.lineNumber, pos.start ) );
}
if ( isAlpha( pos ) )
{
// A macro
String descr = getMacro( 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, COLON ) )
{
pos.start++;
// Now, the OID
String numericOid = getPartialNumericOid( pos );
String realOid = objectIdentifierMacros.get( descr ).getRawOidOrNameSuffix() + DOT + numericOid;
macro.setName( name );
macro.setRawOidOrNameSuffix( realOid );
objectIdentifierMacros.put( name, macro );
}
}
else if ( isDigit( pos ) )
{
// An oid
String numericOid = getNumericOid( pos );
macro.setRawOidOrNameSuffix( numericOid );
macro.setName( name );
objectIdentifierMacros.put( name, macro );
}
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.
*
* @param reader The stream reader
* @throws LdapSchemaException If something went wrong in the schema
* @throws IOException If the stream can't be read
*/
public 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();
AttributeType attributeType = parseAttributeTypeStrict( reader, pos, objectIdentifierMacros );
schemaDescriptions.add( attributeType );
}
else if ( startsWith( pos, "objectclass" ) )
{
pos.start += "objectclass".length();
ObjectClass objectClass = parseObjectClassStrict( reader, pos, objectIdentifierMacros );
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 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;
}
}