You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by er...@apache.org on 2006/12/10 22:15:26 UTC

svn commit: r485262 - in /directory/trunks/shared/ldap: ./ src/main/antlr/ src/main/java/org/apache/directory/shared/ldap/schema/syntax/ src/test/java/org/apache/directory/shared/ldap/schema/syntax/

Author: ersiner
Date: Sun Dec 10 13:15:25 2006
New Revision: 485262

URL: http://svn.apache.org/viewvc?view=rev&rev=485262
Log:
Applying Stefan Seelmann's patch for DIRSERVER-793.
Thank you Stefan!

Added:
    directory/trunks/shared/ldap/src/main/antlr/schema-extension.g
    directory/trunks/shared/ldap/src/main/antlr/schema-qdstring.g
    directory/trunks/shared/ldap/src/main/antlr/schema-value.g
    directory/trunks/shared/ldap/src/main/antlr/schema.g
    directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/schema/syntax/ObjectClassDescription.java
    directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/schema/syntax/ObjectClassDescriptionSyntaxChecker.java
    directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/schema/syntax/SchemaParser.java
    directory/trunks/shared/ldap/src/test/java/org/apache/directory/shared/ldap/schema/syntax/ObjectClassDescriptionSyntaxCheckerTest.java
    directory/trunks/shared/ldap/src/test/java/org/apache/directory/shared/ldap/schema/syntax/SchemaParserObjectClassDescriptionTest.java
Modified:
    directory/trunks/shared/ldap/pom.xml

Modified: directory/trunks/shared/ldap/pom.xml
URL: http://svn.apache.org/viewvc/directory/trunks/shared/ldap/pom.xml?view=diff&rev=485262&r1=485261&r2=485262
==============================================================================
--- directory/trunks/shared/ldap/pom.xml (original)
+++ directory/trunks/shared/ldap/pom.xml Sun Dec 10 13:15:25 2006
@@ -115,7 +115,7 @@
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-antlr-plugin</artifactId>
         <configuration>
-          <grammars>filter-value-lexer.g filter-lexer.g filter-lexer.g filter-value-parser.g filter-parser.g subtree-specification.g ACIItem.g TriggerSpecification.g</grammars>
+          <grammars>schema-value.g schema-qdstring.g schema-extension.g schema.g filter-value-lexer.g filter-lexer.g filter-lexer.g filter-value-parser.g filter-parser.g subtree-specification.g ACIItem.g TriggerSpecification.g</grammars>
         </configuration>
         <executions>
            <execution>

Added: directory/trunks/shared/ldap/src/main/antlr/schema-extension.g
URL: http://svn.apache.org/viewvc/directory/trunks/shared/ldap/src/main/antlr/schema-extension.g?view=auto&rev=485262
==============================================================================
--- directory/trunks/shared/ldap/src/main/antlr/schema-extension.g (added)
+++ directory/trunks/shared/ldap/src/main/antlr/schema-extension.g Sun Dec 10 13:15:25 2006
@@ -0,0 +1,108 @@
+header {
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License. 
+ *  
+ */
+package org.apache.directory.shared.ldap.schema.syntax;
+
+import java.io.* ;
+import java.util.* ;
+
+}
+
+
+   
+/**
+ * An antlr generated schema lexer. This is a sub-lexer.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+class AntlrSchemaExtensionLexer extends Lexer;
+
+options    {
+    k = 2 ;
+    exportVocab=AntlrSchemaExtension ;
+    charVocabulary = '\u0000'..'\uFFFE'; 
+    caseSensitive = true ;
+    defaultErrorHandler = false ;
+}
+
+protected WHSP : (options{greedy=true;}: ' ' )+ {$setType(Token.SKIP);} ;
+protected QUOTE : '\'' ;
+//protected ESC : '\\' ;
+
+XKEY : xstring:XSTRING { setText(xstring.getText().trim()); }; 
+XVALUES : values:VALUES { setText(values.getText().trim()); };
+
+protected XSTRING : ( "X-" ( 'a'..'z' | 'A'..'Z' | '-' | '_' )+ WHSP ) ; 
+protected VALUES : ( VALUE | '('  VALUE ( ('$')? VALUE )* ')' ) ;
+protected VALUE : (WHSP)? ( QUOTED_STRING ) (options {greedy=true;}: WHSP)? ;
+protected QUOTED_STRING : ( QUOTE (~'\'')* QUOTE ) ;
+
+
+
+
+
+
+/**
+ * An antlr generated schema parser. This is a sub-parser used to parse
+ * extensions according to RFC4512.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+class AntlrSchemaExtensionParser extends Parser;
+options    {
+    k = 3 ;
+    defaultErrorHandler = false ;
+    //buildAST=true ;
+}
+
+
+    /**
+     * extensions = *( SP xstring SP qdstrings )
+     * xstring = "X" HYPHEN 1*( ALPHA / HYPHEN / USCORE )
+     */
+extension returns [AntlrSchemaParser.Extension extension = new AntlrSchemaParser.Extension()]
+    :
+    ( xkey:XKEY { extension.setKey(xkey.getText()); } )
+    ( xvalues:XVALUES { extension.setValues(qdstrings(xvalues.getText())); } )
+    ;
+    
+    
+qdstrings [String s] returns [List<String> qdstrings]
+    {
+        try 
+        {
+    	    AntlrSchemaQdstringLexer lexer = new AntlrSchemaQdstringLexer(new StringReader(s));
+            AntlrSchemaQdstringParser parser = new AntlrSchemaQdstringParser(lexer);
+            qdstrings = parser.qdstrings();
+        }
+        catch (RecognitionException re) {
+            re.printStackTrace();
+            throw re;
+        }
+        catch (TokenStreamException tse) {
+            tse.printStackTrace();
+            throw tse;
+        }
+    }
+    :
+    ;
+

Added: directory/trunks/shared/ldap/src/main/antlr/schema-qdstring.g
URL: http://svn.apache.org/viewvc/directory/trunks/shared/ldap/src/main/antlr/schema-qdstring.g?view=auto&rev=485262
==============================================================================
--- directory/trunks/shared/ldap/src/main/antlr/schema-qdstring.g (added)
+++ directory/trunks/shared/ldap/src/main/antlr/schema-qdstring.g Sun Dec 10 13:15:25 2006
@@ -0,0 +1,126 @@
+header {
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License. 
+ *  
+ */
+package org.apache.directory.shared.ldap.schema.syntax;
+
+import java.util.* ;
+
+}
+
+   
+/**
+ * An antlr generated schema lexer. This is a sub-lexer.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+class AntlrSchemaQdstringLexer extends Lexer;
+
+options    {
+    k = 2 ;
+    exportVocab=AntlrSchemaQdstring ;
+    charVocabulary = '\u0000'..'\uFFFE'; 
+    caseSensitive = true ;
+    defaultErrorHandler = false ;
+}
+
+WHSP : ( ' ' ) {$setType(Token.SKIP);} ;
+LPAR : '(' ;
+RPAR : ')' ;
+QUOTE : '\'' ;
+QDSTRING : ( QUOTE (~'\'')* QUOTE ) ;
+
+
+
+
+
+/**
+ * An antlr generated schema parser. This is a sub-parser used to parse
+ * qdstring and qdstrings according to RFC4512.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+class AntlrSchemaQdstringParser extends Parser;
+options    {
+    k = 3 ;
+    defaultErrorHandler = false ;
+    //buildAST=true ;
+}
+
+    /**
+     * qdstrings = qdstring / ( LPAREN WSP qdstringlist WSP RPAREN )
+     * qdstringlist = [ qdstring *( SP qdstring ) ]
+     */
+qdstrings returns [List<String> qdstrings]
+    {
+    	qdstrings = new ArrayList<String>();
+        String qdstring = null;
+    }
+    :
+    (
+        ( 
+	        q:QDSTRING 
+	        { 
+	            qdstring = q.getText(); 
+	            if(qdstring.startsWith("'")) {
+	    			qdstring = qdstring.substring(1, qdstring.length());
+	    		}
+	    		if(qdstring.endsWith("'")) {
+	    			qdstring = qdstring.substring(0, qdstring.length()-1);
+	    		}
+	    		qdstrings.add(qdstring);
+	        } 
+        )
+    |
+        ( LPAR qdstring=qdstring { qdstrings.add(qdstring); } ( qdstring=qdstring { qdstrings.add(qdstring); } )* RPAR )
+    )
+    ;
+
+    /**
+     * qdstring = SQUOTE dstring SQUOTE
+     * dstring = 1*( QS / QQ / QUTF8 )   ; escaped UTF-8 string
+     *
+     * QQ =  ESC %x32 %x37 ; "\27"
+     * QS =  ESC %x35 ( %x43 / %x63 ) ; "\5C" / "\5c"
+     *
+     * ; Any UTF-8 encoded Unicode character
+     * ; except %x27 ("\'") and %x5C ("\")
+     * QUTF8    = QUTF1 / UTFMB
+     *
+     * ; Any ASCII character except %x27 ("\'") and %x5C ("\")
+     * QUTF1    = %x00-26 / %x28-5B / %x5D-7F
+     */    
+qdstring returns [String qdstring=null]
+    : 
+    ( 
+        q:QDSTRING 
+        { 
+            qdstring = q.getText(); 
+            if(qdstring.startsWith("'")) {
+    			qdstring = qdstring.substring(1, qdstring.length());
+    		}
+    		if(qdstring.endsWith("'")) {
+    			qdstring = qdstring.substring(0, qdstring.length()-1);
+    		}
+        } 
+    )
+    ; 
+

Added: directory/trunks/shared/ldap/src/main/antlr/schema-value.g
URL: http://svn.apache.org/viewvc/directory/trunks/shared/ldap/src/main/antlr/schema-value.g?view=auto&rev=485262
==============================================================================
--- directory/trunks/shared/ldap/src/main/antlr/schema-value.g (added)
+++ directory/trunks/shared/ldap/src/main/antlr/schema-value.g Sun Dec 10 13:15:25 2006
@@ -0,0 +1,182 @@
+header {
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License. 
+ *  
+ */
+package org.apache.directory.shared.ldap.schema.syntax;
+
+import java.util.* ;
+
+}
+
+
+/**
+ * An antlr generated schema lexer. This is a sub-lexer.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+class AntlrSchemaValueLexer extends Lexer;
+
+options    {
+    k = 2 ;
+    exportVocab=AntlrSchemaValue ;
+    charVocabulary = '\3'..'\377' ;
+    caseSensitive = true ;
+    defaultErrorHandler = false ;
+}
+
+//WHSP : (' ') {$setType(Token.SKIP);} ;
+SP : ( ' ' )+ { setText(" "); };
+
+LPAR : '(' ;
+RPAR : ')' ;
+
+QUOTE : '\'' ;
+DOLLAR : '$' ;
+LBRACKET : '{' ;
+RBRACKET : '}' ;
+LEN : LBRACKET (DIGIT)+ RBRACKET ;
+DIGIT : ('0'..'9') ; 
+NUMERICOID : ('0'..'9')+ ( '.' ('0'..'9')+ )+ ;
+DESCR : ( 'a'..'z' | 'A'..'Z' ) ( 'a'..'z' | 'A'..'Z' | '0'..'9' | '-' )* ;
+
+
+
+
+
+/**
+ * An antlr generated schema parser. This is a sub-parser used to parse
+ * numericoid, oid, oids, qdescr, qdescrs according to RFC4512.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+class AntlrSchemaValueParser extends Parser;
+options    {
+    k = 3 ;
+    defaultErrorHandler = false ;
+    //buildAST=true ;
+}
+
+
+    /**
+     * numericoid = number 1*( DOT number )
+     */
+numericoid returns [String numericoid=null]
+    : 
+    (
+        (SP)? n:NUMERICOID (SP)? { numericoid = n.getText(); } 
+    )
+    ;
+
+
+    /**
+     * oid = descr / numericoid
+     * numericoid = number 1*( DOT number )
+     * descr = keystring
+     * keystring = leadkeychar *keychar
+     * leadkeychar = ALPHA
+     * keychar = ALPHA / DIGIT / HYPHEN
+     * number  = DIGIT / ( LDIGIT 1*DIGIT )
+     *
+     */
+oid returns [String oid=null]
+    : 
+    (
+        (SP)? 
+	    (
+	        n:NUMERICOID { oid = n.getText(); }
+	    | 
+	        d:DESCR { oid = d.getText(); }
+	    )
+        (SP)?
+    )
+    ;
+
+
+    /**
+     * oids = oid / ( LPAREN WSP oidlist WSP RPAREN )
+     * oidlist = oid *( WSP DOLLAR WSP oid )
+     */
+oids returns [List<String> oids]
+    {
+        oids = new ArrayList<String>();
+        String oid = null;
+    }
+    :
+    (
+        ( 
+        	oid=oid { oids.add(oid); } 
+    	)
+    |
+        ( 
+        	LPAR 
+        	oid=oid { oids.add(oid); } 
+        	( 
+        		DOLLAR 
+        		oid=oid { oids.add(oid); } 
+        	)* 
+        	RPAR 
+        )
+    )
+    ;
+
+
+    /**
+     * qdescr = SQUOTE descr SQUOTE
+     */
+qdescr returns [String qdescr=null]
+    : 
+    ( 
+		(SP)?
+        QUOTE 
+        d:DESCR { qdescr = d.getText(); } 
+        QUOTE
+    )
+    ; 
+
+
+    /**
+     * qdescrs = qdescr / ( LPAREN WSP qdescrlist WSP RPAREN )
+     * qdescrlist = [ qdescr *( SP qdescr ) ]
+     */
+qdescrs returns [List<String> qdescrs]
+    {
+    	qdescrs = new ArrayList<String>();
+        String qdescr = null;
+    }
+    :
+    (
+        ( 
+        	qdescr=qdescr { qdescrs.add(qdescr); } 
+    	)
+    |
+        ( 
+        	LPAR 
+        	qdescr=qdescr { qdescrs.add(qdescr); } 
+        	(
+        		SP
+        		qdescr=qdescr { qdescrs.add(qdescr); } 
+    		)* 
+    		(SP)?
+    		RPAR 
+		)
+    )
+    ;
+    

Added: directory/trunks/shared/ldap/src/main/antlr/schema.g
URL: http://svn.apache.org/viewvc/directory/trunks/shared/ldap/src/main/antlr/schema.g?view=auto&rev=485262
==============================================================================
--- directory/trunks/shared/ldap/src/main/antlr/schema.g (added)
+++ directory/trunks/shared/ldap/src/main/antlr/schema.g Sun Dec 10 13:15:25 2006
@@ -0,0 +1,276 @@
+header {
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License. 
+ *  
+ */
+package org.apache.directory.shared.ldap.schema.syntax;
+
+import java.io.* ;
+import java.util.* ;
+
+}
+
+/**
+ * An antlr generated schema main lexer.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+class AntlrSchemaLexer extends Lexer;
+
+options    {
+    k = 5 ;
+    exportVocab=AntlrSchema ;
+    charVocabulary = '\u0000'..'\uFFFE'; 
+    caseSensitive = true ;
+    defaultErrorHandler = false ;
+}
+
+WHSP : (options{greedy=true;}: ' ' )+ {$setType(Token.SKIP);} ;
+
+LPAR : '(' ;
+RPAR : ')' ;
+QUOTE : '\'' ;
+DOLLAR : '$' ;
+LBRACKET : '{' ;
+RBRACKET : '}' ;
+
+LEN : LBRACKET ('0'..'9')+ RBRACKET ;
+
+USERAPPLICATIONS : "userApplications" ;
+DIRECTORYOPERATION : "directoryOperation" ;
+DISTRIBUTEDOPERATION : "distributedOperation" ;
+DSAOPERATION : "dSAOperation" ;
+
+SINGLE_VALUE : ( "SINGLE-VALUE" (WHSP)? ) ;
+COLLECTIVE : ( "COLLECTIVE" (WHSP)? ) ;
+NO_USER_MODIFICATION : ( "NO-USER-MODIFICATION" (WHSP)? ) ;
+
+OBSOLETE : ( "OBSOLETE" (WHSP)? ) ;
+ABSTRACT : ( "ABSTRACT" (WHSP)? ) ;
+STRUCTURAL : ( "STRUCTURAL" (WHSP)? ) ;
+AUXILIARY : ( "AUXILIARY" (WHSP)? ) ;
+
+STARTNUMERICOID : ( LPAR ( numericoid:VALUE ) ) { setText(numericoid.getText().trim()); } ;
+NAME : ( "NAME" WHSP qdstrings:VALUES ) { setText(qdstrings.getText().trim()); } ;
+DESC : ( "DESC" WHSP qdstring:VALUES ) { setText(qdstring.getText().trim()); } ;
+SUP : ( "SUP" WHSP sup:VALUES ) { setText(sup.getText().trim()); } ;
+MUST : ( "MUST" WHSP must:VALUES ) { setText(must.getText().trim()); } ;
+MAY : ( "MAY" WHSP may:VALUES ) { setText(may.getText()); } ;
+EQUALITY : ( "EQUALITY" WHSP equality:VALUES ) { setText(equality.getText().trim()); } ;
+ORDERING : ( "ORDERING" WHSP ordering:VALUES ) { setText(ordering.getText().trim()); } ;
+SUBSTR : ( "SUBSTR" WHSP substr:VALUES ) { setText(substr.getText().trim()); } ;
+SYNTAX : ( "SYNTAX" WHSP syntax:VALUES (len:LEN)? ) { setText(syntax.getText().trim() + (len!=null?len.getText().trim():"")); } ;
+USAGE : ( "USAGE" WHSP op:VALUES ) { setText(op.getText().trim()); } ;
+APPLIES : ( "APPLIES" WHSP applies:VALUES ) { setText(applies.getText().trim()); } ;
+EXTENSION : x:( "X-" ( 'a'..'z' | 'A'..'Z' | '-' | '_' )+ WHSP VALUES ) ; 
+
+protected VALUES : ( VALUE | LPAR  VALUE ( (DOLLAR)? VALUE )* RPAR ) ;
+protected VALUE : (WHSP)? ( QUOTED_STRING | UNQUOTED_STRING ) (options {greedy=true;}: WHSP)? ;
+protected UNQUOTED_STRING : (options{greedy=true;}: 'a'..'z' | 'A'..'Z' | '0'..'9' | '-' | ';' | '.' )+ ;
+protected QUOTED_STRING : ( QUOTE (~'\'')* QUOTE ) ;
+
+
+/**
+ * An antlr generated schema main parser.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+class AntlrSchemaParser extends Parser;
+options    {
+    k = 3 ;
+    defaultErrorHandler = false ;
+    //buildAST=true ;
+}
+
+{
+	static class Extension
+	{
+	
+	    private String key;
+	    
+	    private List<String> values;
+	    
+	    public Extension()
+	    {
+	        this.key = "";
+	        this.values = new ArrayList<String>();
+	    }
+	
+	    public String getKey()
+	    {
+	        return key;
+	    }
+	
+	    public void setKey( String key )
+	    {
+	        this.key = key;
+	    }
+	
+	    public List<String> getValues()
+	    {
+	        return values;
+	    }
+	
+	    public void setValues( List<String> values )
+	    {
+	        this.values = values;
+	    }
+	    
+	    
+	    public void addValue( String value )
+	    {
+	        this.values.add( value );
+	    }
+	    
+	}
+}
+
+
+    /**
+     * Production for matching object class 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"
+     * 
+     * extensions = *( SP xstring SP qdstrings )
+     * xstring = "X" HYPHEN 1*( ALPHA / HYPHEN / USCORE ) 
+     * </pre>
+    */
+objectClassDescription returns [ObjectClassDescription ocd = new ObjectClassDescription()]
+    :
+    ( oid:STARTNUMERICOID { ocd.setOid(numericoid(oid.getText())); } )
+    (
+	    ( name:NAME { ocd.setNames(qdescrs(name.getText())); } )
+	    |
+	    ( desc:DESC { ocd.setDescription(qdstring(desc.getText())); } )
+	    |
+	    ( OBSOLETE { ocd.setObsolete( true ); } )
+	    |
+	    ( sup:SUP { ocd.setSuperiorObjectClasses(oids(sup.getText())); } )
+	    |
+	    ( ABSTRACT { ocd.setKind( ObjectClassDescription.Kind.ABSTRACT ); }
+	      |
+	      STRUCTURAL { ocd.setKind( ObjectClassDescription.Kind.STRUCTURAL ); }
+	      |
+	      AUXILIARY { ocd.setKind( ObjectClassDescription.Kind.AUXILIARY ); } 
+	    )
+	    |
+	    ( must:MUST { ocd.setMustAttributeTypes(oids(must.getText())); } )
+	    |
+	    ( may:MAY { ocd.setMayAttributeTypes(oids(may.getText())); } )
+	    |
+	    ( extension:EXTENSION { 
+	        Extension ex = extension(extension.getText());
+	        ocd.addExtension(ex.getKey(), ex.getValues()); 
+	     } )
+	)*    
+    RPAR
+    ;
+
+
+extension [String s] returns [Extension extension]
+    {
+        extension = new Extension();
+        AntlrSchemaExtensionLexer lexer = new AntlrSchemaExtensionLexer(new StringReader(s));
+        AntlrSchemaExtensionParser parser = new AntlrSchemaExtensionParser(lexer);
+        extension = parser.extension();
+    }
+    :
+    ;
+
+
+numericoid [String s] returns [String numericoid]
+    {
+    	AntlrSchemaValueLexer lexer = new AntlrSchemaValueLexer(new StringReader(s));
+        AntlrSchemaValueParser parser = new AntlrSchemaValueParser(lexer);
+        numericoid = parser.numericoid();
+    }
+    :
+    ;
+
+oid [String s] returns [String oid]
+    {
+    	AntlrSchemaValueLexer lexer = new AntlrSchemaValueLexer(new StringReader(s));
+        AntlrSchemaValueParser parser = new AntlrSchemaValueParser(lexer);
+        oid = parser.oid();
+    }
+    :
+    ;
+
+oids [String s] returns [List<String> oids]
+    {
+    	AntlrSchemaValueLexer lexer = new AntlrSchemaValueLexer(new StringReader(s));
+        AntlrSchemaValueParser parser = new AntlrSchemaValueParser(lexer);
+        oids = parser.oids();
+    }
+    :
+    ;
+
+qdescr [String s] returns [String qdescr]
+    {
+    	AntlrSchemaValueLexer lexer = new AntlrSchemaValueLexer(new StringReader(s));
+        AntlrSchemaValueParser parser = new AntlrSchemaValueParser(lexer);
+        qdescr = parser.qdescr();
+    }
+    :
+    ;
+
+qdescrs [String s] returns [List<String> qdescrs]
+    {
+    	AntlrSchemaValueLexer lexer = new AntlrSchemaValueLexer(new StringReader(s));
+        AntlrSchemaValueParser parser = new AntlrSchemaValueParser(lexer);
+        qdescrs = parser.qdescrs();
+    }
+    :
+    ;
+
+qdstring [String s] returns [String qdstring]
+    {
+    	AntlrSchemaQdstringLexer lexer = new AntlrSchemaQdstringLexer(new StringReader(s));
+        AntlrSchemaQdstringParser parser = new AntlrSchemaQdstringParser(lexer);
+        qdstring = parser.qdstring();
+    }
+    :
+    ;
+
+qdstrings [String s] returns [List<String> qdstrings]
+    {
+    	AntlrSchemaQdstringLexer lexer = new AntlrSchemaQdstringLexer(new StringReader(s));
+        AntlrSchemaQdstringParser parser = new AntlrSchemaQdstringParser(lexer);
+        qdstrings = parser.qdstrings();
+    }
+    :
+    ;
+
+    
+
+    
\ No newline at end of file

Added: directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/schema/syntax/ObjectClassDescription.java
URL: http://svn.apache.org/viewvc/directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/schema/syntax/ObjectClassDescription.java?view=auto&rev=485262
==============================================================================
--- directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/schema/syntax/ObjectClassDescription.java (added)
+++ directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/schema/syntax/ObjectClassDescription.java Sun Dec 10 13:15:25 2006
@@ -0,0 +1,206 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License. 
+ *  
+ */
+
+package org.apache.directory.shared.ldap.schema.syntax;
+
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * RFC 4512 - 4.1.1. Object Class Description
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class ObjectClassDescription
+{
+    public static enum Kind
+    {
+        ABSTRACT, STRUCTURAL, AUXILIARY
+    }
+
+    private String oid;
+
+    private List<String> names;
+
+    private String description;
+
+    private boolean isObsolete;
+
+    private List<String> superiorObjectClasses;
+
+    private Kind kind;
+
+    private List<String> mustAttributeTypes;
+
+    private List<String> mayAttributeTypes;
+
+    private Map<String, List<String>> extensions;
+
+
+    public ObjectClassDescription()
+    {
+        this.oid = "";
+        names = new ArrayList<String>();
+        description = "";
+        isObsolete = false;
+        superiorObjectClasses = new ArrayList<String>();
+        kind = Kind.STRUCTURAL;
+        mustAttributeTypes = new ArrayList<String>();
+        mayAttributeTypes = new ArrayList<String>();
+        extensions = new LinkedHashMap<String, List<String>>();
+    }
+
+
+    public String getDescription()
+    {
+        return description;
+    }
+
+
+    public void setDescription( String description )
+    {
+        this.description = description;
+    }
+
+
+    public Map<String, List<String>> getExtensions()
+    {
+        return extensions;
+    }
+
+
+    public void setExtensions( Map<String, List<String>> extensions )
+    {
+        this.extensions = extensions;
+    }
+
+
+    public boolean isObsolete()
+    {
+        return isObsolete;
+    }
+
+
+    public void setObsolete( boolean isObsolete )
+    {
+        this.isObsolete = isObsolete;
+    }
+
+
+    public List<String> getMayAttributeTypes()
+    {
+        return mayAttributeTypes;
+    }
+
+
+    public void setMayAttributeTypes( List<String> mayAttributeTypes )
+    {
+        this.mayAttributeTypes = mayAttributeTypes;
+    }
+
+
+    public List<String> getMustAttributeTypes()
+    {
+        return mustAttributeTypes;
+    }
+
+
+    public void setMustAttributeTypes( List<String> mustAttributeTypes )
+    {
+        this.mustAttributeTypes = mustAttributeTypes;
+    }
+
+
+    public List<String> getNames()
+    {
+        return names;
+    }
+
+
+    public void setNames( List<String> names )
+    {
+        this.names = names;
+    }
+
+
+    public String getNumericOid()
+    {
+        return oid;
+    }
+
+
+    public void setOid( String oid )
+    {
+        this.oid = oid;
+    }
+
+
+    public List<String> getSuperiorObjectClasses()
+    {
+        return superiorObjectClasses;
+    }
+
+
+    public void setSuperiorObjectClasses( List<String> superiorObjectClasses )
+    {
+        this.superiorObjectClasses = superiorObjectClasses;
+    }
+
+
+    public Kind getKind()
+    {
+        return kind;
+    }
+
+
+    public void setKind( Kind kind )
+    {
+        this.kind = kind;
+    }
+
+
+    public void addSuperiorObjectClass( String oid )
+    {
+        this.superiorObjectClasses.add( oid );
+    }
+
+
+    public void addMustAttributeType( String oid )
+    {
+        this.mustAttributeTypes.add( oid );
+    }
+
+
+    public void addMayAttributeType( String oid )
+    {
+        this.mayAttributeTypes.add( oid );
+    }
+
+
+    public void addExtension( String key, List<String> values )
+    {
+        this.extensions.put( key, values );
+    }
+
+}
\ No newline at end of file

Added: directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/schema/syntax/ObjectClassDescriptionSyntaxChecker.java
URL: http://svn.apache.org/viewvc/directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/schema/syntax/ObjectClassDescriptionSyntaxChecker.java?view=auto&rev=485262
==============================================================================
--- directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/schema/syntax/ObjectClassDescriptionSyntaxChecker.java (added)
+++ directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/schema/syntax/ObjectClassDescriptionSyntaxChecker.java Sun Dec 10 13:15:25 2006
@@ -0,0 +1,137 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License. 
+ *  
+ */
+package org.apache.directory.shared.ldap.schema.syntax;
+
+
+import java.text.ParseException;
+
+import javax.naming.NamingException;
+
+import org.apache.directory.shared.ldap.exception.LdapInvalidAttributeValueException;
+import org.apache.directory.shared.ldap.message.ResultCodeEnum;
+import org.apache.directory.shared.ldap.util.StringTools;
+
+
+/**
+ * A SyntaxChecker which verifies that a value follows the
+ * object class descripton syntax according to RFC 4512, par 4.4.1:
+ * 
+ * <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"
+ * 
+ * extensions = *( SP xstring SP qdstrings )
+ * xstring = "X" HYPHEN 1*( ALPHA / HYPHEN / USCORE )
+ * </pre>
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+public class ObjectClassDescriptionSyntaxChecker implements SyntaxChecker
+{
+
+    /** The Syntax OID, according to RFC 4517, par. 3.3.24 */
+    public static final String OID = "1.3.6.1.4.1.1466.115.121.1.37";
+
+    private SchemaParser schemaParser;
+
+
+    /**
+     * 
+     * Creates a new instance of ObjectClassDescriptionSyntaxChecker.
+     *
+     */
+    public ObjectClassDescriptionSyntaxChecker()
+    {
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.apache.directory.shared.ldap.schema.SyntaxChecker#getSyntaxOid()
+     */
+    public String getSyntaxOid()
+    {
+        return OID;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.apache.directory.shared.ldap.schema.SyntaxChecker#assertSyntax(java.lang.Object)
+     */
+    public void assertSyntax( Object value ) throws NamingException
+    {
+        if ( !isValidSyntax( value ) )
+        {
+            throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX );
+        }
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.apache.directory.shared.ldap.schema.SyntaxChecker#isValidSyntax(java.lang.Object)
+     */
+    public boolean isValidSyntax( Object value )
+    {
+        String strValue;
+
+        if ( value == null )
+        {
+            return false;
+        }
+
+        if ( value instanceof String )
+        {
+            strValue = ( String ) value;
+        }
+        else if ( value instanceof byte[] )
+        {
+            strValue = StringTools.utf8ToString( ( byte[] ) value );
+        }
+        else
+        {
+            strValue = value.toString();
+        }
+
+        if ( schemaParser == null )
+        {
+            schemaParser = new SchemaParser();
+        }
+
+        try
+        {
+            schemaParser.parseObjectClassDescription( strValue );
+            return true;
+        }
+        catch ( ParseException pe )
+        {
+            return false;
+        }
+    }
+}

Added: directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/schema/syntax/SchemaParser.java
URL: http://svn.apache.org/viewvc/directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/schema/syntax/SchemaParser.java?view=auto&rev=485262
==============================================================================
--- directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/schema/syntax/SchemaParser.java (added)
+++ directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/schema/syntax/SchemaParser.java Sun Dec 10 13:15:25 2006
@@ -0,0 +1,214 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License. 
+ *  
+ */
+package org.apache.directory.shared.ldap.schema.syntax;
+
+
+import java.io.Reader;
+import java.io.StringReader;
+import java.text.ParseException;
+
+import antlr.CharBuffer;
+import antlr.LexerSharedInputState;
+import antlr.RecognitionException;
+import antlr.TokenStream;
+import antlr.TokenStreamException;
+
+
+/**
+ * A parser for RFC 4512 schema objects
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class SchemaParser
+{
+
+    /** the antlr generated parser being wrapped */
+    private ReusableAntlrSchemaParser parser;
+
+    /** the antlr generated lexer being wrapped */
+    private ReusableAntlrSchemaLexer lexer;
+
+
+    /**
+     * Creates a schema parser instance.
+     */
+    public SchemaParser()
+    {
+        this.lexer = new ReusableAntlrSchemaLexer( new StringReader( "" ) );
+        this.parser = new ReusableAntlrSchemaParser( lexer );
+    }
+
+
+    /**
+     * Initializes the plumbing by creating a pipe and coupling the parser/lexer
+     * pair with it. param spec the specification to be parsed
+     */
+    private synchronized void reset( String spec )
+    {
+        StringReader in = new StringReader( spec );
+        this.lexer.prepareNextInput( in );
+        this.parser.resetState();
+    }
+
+
+    /**
+     * Parses a object class definition according to RFC 4512:
+     * 
+     * <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"
+     * 
+     * extensions = *( SP xstring SP qdstrings )
+     * xstring = "X" HYPHEN 1*( ALPHA / HYPHEN / USCORE ) 
+     * </pre>
+     * 
+     * @param objectClassDescription the object class description to be parsed
+     * @return the parsed ObjectClassDescription bean
+     * @throws ParseException if there are any recognition errors (bad syntax)
+     */
+    public synchronized ObjectClassDescription parseObjectClassDescription( String objectClassDescription )
+        throws ParseException
+    {
+
+        if ( objectClassDescription == null )
+        {
+            throw new ParseException( "Null", 0 );
+        }
+
+        reset( objectClassDescription ); // reset and initialize the parser / lexer pair
+
+        try
+        {
+            ObjectClassDescription ocd = parser.objectClassDescription();
+            return ocd;
+        }
+        catch ( RecognitionException re )
+        {
+            String msg = "Parser failure on object class description:\n\t" + objectClassDescription;
+            msg += "\nAntlr message: " + re.getMessage();
+            msg += "\nAntlr column: " + re.getColumn();
+            throw new ParseException( msg, re.getColumn() );
+        }
+        catch ( TokenStreamException tse )
+        {
+            String msg = "Parser failure on object class description:\n\t" + objectClassDescription;
+            msg += "\nAntlr message: " + tse.getMessage();
+            throw new ParseException( msg, 0 );
+        }
+
+    }
+
+    /**
+     * A reusable lexer class extended from antlr generated lexer for an LDAP
+     * schema as defined in RFC 4512. This class
+     * enables the reuse of the antlr lexer without having to recreate the it every
+     * time as stated in <a
+     * href="http://www.antlr.org:8080/pipermail/antlr-interest/2003-April/003631.html">
+     * a Antlr Interest Group mail</a> .
+     * 
+     * @see <a href="http://www.faqs.org/rfcs/rfc3672.html">RFC 3672</a>
+     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+     * @version $Rev: 437007 $
+     */
+    class ReusableAntlrSchemaLexer extends AntlrSchemaLexer
+    {
+        private boolean savedCaseSensitive;
+
+        private boolean savedCaseSensitiveLiterals;
+
+
+        /**
+         * Creates a ReusableAntlrSchemaLexer instance.
+         * 
+         * @param in
+         *            the input to the lexer
+         */
+        public ReusableAntlrSchemaLexer( Reader in )
+        {
+            super( in );
+            savedCaseSensitive = getCaseSensitive();
+            savedCaseSensitiveLiterals = getCaseSensitiveLiterals();
+        }
+
+
+        /**
+         * Resets the state of an antlr lexer and initializes it with new input.
+         * 
+         * @param in
+         *            the input to the lexer
+         */
+        public void prepareNextInput( Reader in )
+        {
+            CharBuffer buf = new CharBuffer( in );
+            LexerSharedInputState state = new LexerSharedInputState( buf );
+            this.setInputState( state );
+
+            this.setCaseSensitive( savedCaseSensitive );
+
+            // no set method for this protected field.
+            this.caseSensitiveLiterals = savedCaseSensitiveLiterals;
+        }
+    }
+
+    /**
+     * A reusable parser class extended from antlr generated parser for an LDAP
+     * schema as defined in RFC 4512. This class
+     * enables the reuse of the antlr parser without having to recreate the it every
+     * time as stated in <a
+     * href="http://www.antlr.org:8080/pipermail/antlr-interest/2003-April/003631.html">
+     * a Antlr Interest Group mail</a> .
+     * 
+     * @see <a href="http://www.faqs.org/rfcs/rfc3672.html">RFC 3672</a>
+     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+     * @version $Rev: 437007 $
+     */
+    class ReusableAntlrSchemaParser extends AntlrSchemaParser
+    {
+        /**
+         * Creates a ReusableAntlrSchemaParser instance.
+         */
+        public ReusableAntlrSchemaParser( TokenStream lexer )
+        {
+            super( lexer );
+        }
+
+
+        /**
+         * Resets the state of an antlr parser.
+         */
+        public void resetState()
+        {
+            // no set method for this protected field.
+            this.traceDepth = 0;
+
+            this.getInputState().reset();
+        }
+    }
+}

Added: directory/trunks/shared/ldap/src/test/java/org/apache/directory/shared/ldap/schema/syntax/ObjectClassDescriptionSyntaxCheckerTest.java
URL: http://svn.apache.org/viewvc/directory/trunks/shared/ldap/src/test/java/org/apache/directory/shared/ldap/schema/syntax/ObjectClassDescriptionSyntaxCheckerTest.java?view=auto&rev=485262
==============================================================================
--- directory/trunks/shared/ldap/src/test/java/org/apache/directory/shared/ldap/schema/syntax/ObjectClassDescriptionSyntaxCheckerTest.java (added)
+++ directory/trunks/shared/ldap/src/test/java/org/apache/directory/shared/ldap/schema/syntax/ObjectClassDescriptionSyntaxCheckerTest.java Sun Dec 10 13:15:25 2006
@@ -0,0 +1,98 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License. 
+ *  
+ */
+package org.apache.directory.shared.ldap.schema.syntax;
+
+
+import junit.framework.TestCase;
+
+
+/**
+ * Test cases for ObjectClassDescriptionSyntaxChecker.
+ * 
+ * There are also many test cases in SchemaParserObjectClassDescriptionTest.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+public class ObjectClassDescriptionSyntaxCheckerTest extends TestCase
+{
+    ObjectClassDescriptionSyntaxChecker checker = new ObjectClassDescriptionSyntaxChecker();
+
+
+    public void testNullString()
+    {
+        assertFalse( checker.isValidSyntax( null ) );
+    }
+
+
+    public void testEmptyString()
+    {
+        assertFalse( checker.isValidSyntax( "" ) );
+    }
+
+
+    public void testOneCharString()
+    {
+        assertFalse( checker.isValidSyntax( "A" ) );
+        assertFalse( checker.isValidSyntax( "1" ) );
+        assertFalse( checker.isValidSyntax( "-" ) );
+        assertFalse( checker.isValidSyntax( "(" ) );
+    }
+
+
+    public void testValid()
+    {
+        assertTrue( checker.isValidSyntax( "( 2.5.6.6 )" ) );
+        assertTrue( checker.isValidSyntax( "( 2.5.6.6 NAME 'person' )" ) );
+        assertTrue( checker.isValidSyntax( "( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' )" ) );
+        assertTrue( checker.isValidSyntax( "( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' SUP top )" ) );
+        assertTrue( checker.isValidSyntax( "( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' SUP top STRUCTURAL )" ) );
+        assertTrue( checker.isValidSyntax( "( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' SUP top STRUCTURAL MUST ( sn $ cn ) )" ) );
+        assertTrue( checker.isValidSyntax( "( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' SUP top STRUCTURAL MUST ( sn $ cn ) MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) )" ) );
+
+        assertTrue( checker.isValidSyntax( "(2.5.6.6)" ) );
+        assertTrue( checker.isValidSyntax( "(      2.5.6.6      NAME      'person'      )" ) );
+    }
+
+
+    public void testInvalid()
+    {
+        // missing/invalid OID
+        assertFalse( checker.isValidSyntax( "()" ) );
+        assertFalse( checker.isValidSyntax( "(  )" ) );
+        assertFalse( checker.isValidSyntax( "( . )" ) );
+        assertFalse( checker.isValidSyntax( "( 1 )" ) );
+        assertFalse( checker.isValidSyntax( "( 1. )" ) );
+        assertFalse( checker.isValidSyntax( "( 1.2. )" ) );
+        assertFalse( checker.isValidSyntax( "( 1.A )" ) );
+        assertFalse( checker.isValidSyntax( "( A.B )" ) );
+
+        // missing right parenthesis
+        assertFalse( checker.isValidSyntax( "( 2.5.6.6 NAME 'person'" ) );
+
+        // missing quotes
+        assertFalse( checker.isValidSyntax( "( 2.5.6.6 NAME person )" ) );
+
+        // lowercase NAME, DESC, SUP
+        assertFalse( checker.isValidSyntax( "( 2.5.6.6 name 'person' desc 'RFC2256: a person' sup top " ) );
+
+    }
+
+}

Added: directory/trunks/shared/ldap/src/test/java/org/apache/directory/shared/ldap/schema/syntax/SchemaParserObjectClassDescriptionTest.java
URL: http://svn.apache.org/viewvc/directory/trunks/shared/ldap/src/test/java/org/apache/directory/shared/ldap/schema/syntax/SchemaParserObjectClassDescriptionTest.java?view=auto&rev=485262
==============================================================================
--- directory/trunks/shared/ldap/src/test/java/org/apache/directory/shared/ldap/schema/syntax/SchemaParserObjectClassDescriptionTest.java (added)
+++ directory/trunks/shared/ldap/src/test/java/org/apache/directory/shared/ldap/schema/syntax/SchemaParserObjectClassDescriptionTest.java Sun Dec 10 13:15:25 2006
@@ -0,0 +1,1072 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License. 
+ *  
+ */
+package org.apache.directory.shared.ldap.schema.syntax;
+
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.directory.shared.ldap.schema.syntax.ObjectClassDescription;
+import org.apache.directory.shared.ldap.schema.syntax.SchemaParser;
+
+
+/**
+ * Tests the SchemaParser class.
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class SchemaParserObjectClassDescriptionTest extends TestCase
+{
+    /** the parser instance */
+    private SchemaParser parser;
+
+    /** holds multithreaded success value */
+    boolean isSuccessMultithreaded = true;
+
+
+    protected void setUp() throws Exception
+    {
+        parser = new SchemaParser();
+    }
+
+
+    protected void tearDown() throws Exception
+    {
+        parser = null;
+    }
+
+
+    /**
+     * Test numericoid
+     * 
+     * @throws ParseException
+     */
+    public void testNumericOid() throws ParseException
+    {
+        String value = null;
+        ObjectClassDescription ocd = null;
+
+        // null test
+        value = null;
+        try
+        {
+            parser.parseObjectClassDescription( value );
+            fail( "Exception expected, null" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // no oid
+        value = "( )";
+        try
+        {
+            parser.parseObjectClassDescription( value );
+            fail( "Exception expected, no NUMERICOID" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // simple
+        value = "( 1.1 )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( "1.1", ocd.getNumericOid() );
+
+        // simple with spaces
+        value = "(          1.1          )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( "1.1", ocd.getNumericOid() );
+
+        // non-numeric not allowed
+        value = "( top )";
+        try
+        {
+            parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid NUMERICOID top" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // to short
+        value = "( 1 )";
+        try
+        {
+            parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid NUMERICOID 1" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // dot only
+        value = "( . )";
+        try
+        {
+            parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid NUMERICOID ." );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // ends with dot
+        value = "( 1.1. )";
+        try
+        {
+            parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid NUMERICOID 1.1." );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // quotes not allowed
+        value = "( '1.1' )";
+        try
+        {
+            parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid NUMERICOID '1.1' (quoted)" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+    }
+
+
+    /**
+     * Tests NAME and its values
+     * 
+     * @throws ParseException
+     */
+    public void testNames() throws ParseException
+    {
+        String value = null;
+        ObjectClassDescription ocd = null;
+
+        // alpha
+        value = "( 1.1 NAME 'test' )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 1, ocd.getNames().size() );
+        assertEquals( "test", ocd.getNames().get( 0 ) );
+
+        // alpha-num-hypen
+        value = "( 1.1 NAME 'a-z-0-9' )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 1, ocd.getNames().size() );
+        assertEquals( "a-z-0-9", ocd.getNames().get( 0 ) );
+
+        // with parentheses
+        value = "( 1.1 NAME ( 'a-z-0-9' ) )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 1, ocd.getNames().size() );
+        assertEquals( "a-z-0-9", ocd.getNames().get( 0 ) );
+
+        // with parentheses, without space
+        value = "( 1.1 NAME ('a-z-0-9') )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 1, ocd.getNames().size() );
+        assertEquals( "a-z-0-9", ocd.getNames().get( 0 ) );
+
+        // multi with space
+        value = "( 1.1 NAME ( 'test' 'a-z-0-9' ) )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 2, ocd.getNames().size() );
+        assertEquals( "test", ocd.getNames().get( 0 ) );
+        assertEquals( "a-z-0-9", ocd.getNames().get( 1 ) );
+
+        // multi without space
+        value = "( 1.1 NAME ('test' 'a-z-0-9' 'top') )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 3, ocd.getNames().size() );
+        assertEquals( "test", ocd.getNames().get( 0 ) );
+        assertEquals( "a-z-0-9", ocd.getNames().get( 1 ) );
+        assertEquals( "top", ocd.getNames().get( 2 ) );
+
+        // multi with many spaces
+        value = "(          1.1          NAME          (          'test'          'a-z-0-9'          'top'          )          )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 3, ocd.getNames().size() );
+        assertEquals( "test", ocd.getNames().get( 0 ) );
+        assertEquals( "a-z-0-9", ocd.getNames().get( 1 ) );
+        assertEquals( "top", ocd.getNames().get( 2 ) );
+
+        // lowercase
+        value = "( 1.1 name 'test' )";
+        try
+        {
+            parser.parseObjectClassDescription( value );
+            fail( "Exception expected, NAME is lowercase" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // unquoted
+        value = "( 1.1 NAME test )";
+        try
+        {
+            parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid NAME test (unquoted)" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // start with number
+        value = "( 1.1 NAME '1test' )";
+        try
+        {
+            parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid NAME 1test (starts with number)" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // start with hypen
+        value = "( 1.1 NAME '-test' )";
+        try
+        {
+            parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid NAME -test (starts with hypen)" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // invalid character
+        value = "( 1.1 NAME 'te_st' )";
+        try
+        {
+            parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid NAME te_st (contains invalid character)" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // NAM unknown
+        value = "( 1.1 NAM 'test' )";
+        try
+        {
+            parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid token NAM" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // one valid, one invalid
+        value = "( 1.1 NAME ( 'test' 'te_st' ) )";
+        try
+        {
+            parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid NAME te_st (contains invalid character)" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // no space between values
+        value = "( 1.1 NAME ( 'test''test2' ) )";
+        try
+        {
+            ocd = parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid NAME values (no space between values)" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+    }
+
+
+    /**
+     * Tests DESC
+     * 
+     * @throws ParseException
+     */
+    public void testDescription() throws ParseException
+    {
+        String value = null;
+        ObjectClassDescription ocd = null;
+
+        // simple
+        value = "(1.1 NAME 'test' DESC 'Descripton')";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( "Descripton", ocd.getDescription() );
+
+        // unicode
+        value = "( 1.1 NAME 'test' DESC 'Descripton äöüß 部長' )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( "Descripton äöüß 部長", ocd.getDescription() );
+
+        // lowercase
+        value = "( 1.1 desc 'Descripton' )";
+        try
+        {
+            parser.parseObjectClassDescription( value );
+            fail( "Exception expected, DESC is lowercase" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+    }
+
+
+    /**
+     * Tests OBSOLETE
+     * 
+     * @throws ParseException
+     */
+    public void testObsolete() throws ParseException
+    {
+        String value = null;
+        ObjectClassDescription ocd = null;
+
+        // not obsolete
+        value = "( 1.1 NAME 'test' DESC 'Descripton' )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertFalse( ocd.isObsolete() );
+
+        // obsolete
+        value = "(1.1 NAME 'test' DESC 'Descripton' OBSOLETE)";
+        ocd = parser.parseObjectClassDescription( value );
+        assertTrue( ocd.isObsolete() );
+
+        // ivalid
+        value = "(1.1 NAME 'test' DESC 'Descripton' OBSOLET )";
+        try
+        {
+            ocd = parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid OBSOLETE value" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+    }
+
+
+    /**
+     * Test SUP and its values.
+     * 
+     * @throws ParseException
+     */
+    public void testSuperior() throws ParseException
+    {
+        String value = null;
+        ObjectClassDescription ocd = null;
+
+        // no SUP
+        value = "( 1.1 )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 0, ocd.getSuperiorObjectClasses().size() );
+
+        // SUP simple numericoid
+        value = "( 1.1 SUP 1.2.3 )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 1, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( "1.2.3", ocd.getSuperiorObjectClasses().get( 0 ) );
+
+        // SUP simple descr
+        value = "( 1.1 SUP top )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 1, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( "top", ocd.getSuperiorObjectClasses().get( 0 ) );
+
+        // SUP single numericoid
+        value = "( 1.1 SUP ( 1.2.3.4.5 ) )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 1, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( "1.2.3.4.5", ocd.getSuperiorObjectClasses().get( 0 ) );
+
+        // SUP single descr
+        value = "( 1.1 SUP ( A-Z-0-9 ) )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 1, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( "A-Z-0-9", ocd.getSuperiorObjectClasses().get( 0 ) );
+
+        // SUP multi numericoid
+        value = "( 1.1 SUP ( 1.2.3 $ 1.2.3.4.5 ) )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 2, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( "1.2.3", ocd.getSuperiorObjectClasses().get( 0 ) );
+        assertEquals( "1.2.3.4.5", ocd.getSuperiorObjectClasses().get( 1 ) );
+
+        // SUP multi descr
+        value = "( 1.1 SUP ( top1 $ top2 ) )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 2, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( "top1", ocd.getSuperiorObjectClasses().get( 0 ) );
+        assertEquals( "top2", ocd.getSuperiorObjectClasses().get( 1 ) );
+
+        // SUP multi mixed
+        value = "( 1.1 SUP ( top1 $ 1.2.3.4 $ top2 ) )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 3, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( "top1", ocd.getSuperiorObjectClasses().get( 0 ) );
+        assertEquals( "1.2.3.4", ocd.getSuperiorObjectClasses().get( 1 ) );
+        assertEquals( "top2", ocd.getSuperiorObjectClasses().get( 2 ) );
+
+        // SUP multi mixed no space
+        value = "( 1.1 SUP (TOP-1$1.2.3.4$TOP-2) )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 3, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( "TOP-1", ocd.getSuperiorObjectClasses().get( 0 ) );
+        assertEquals( "1.2.3.4", ocd.getSuperiorObjectClasses().get( 1 ) );
+        assertEquals( "TOP-2", ocd.getSuperiorObjectClasses().get( 2 ) );
+
+        // SUP multi mixed many spaces
+        value = "(          1.1          SUP          (          top1          $          1.2.3.4$top2          )          )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 3, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( "top1", ocd.getSuperiorObjectClasses().get( 0 ) );
+        assertEquals( "1.2.3.4", ocd.getSuperiorObjectClasses().get( 1 ) );
+        assertEquals( "top2", ocd.getSuperiorObjectClasses().get( 2 ) );
+
+        // no quote allowed
+        value = "( 1.1 SUP 'top' )";
+        try
+        {
+            ocd = parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid SUP 'top' (quoted)" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // no quote allowed
+        value = "( 1.1 SUP '1.2.3.4' )";
+        try
+        {
+            ocd = parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid SUP '1.2.3.4' (quoted)" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // invalid character
+        value = "( 1.1 SUP 1.2.3.4.A )";
+        try
+        {
+            ocd = parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid SUP '1.2.3.4.A' (invalid character)" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // invalid start
+        value = "( 1.1 SUP ( top1 $ -top2 ) )";
+        try
+        {
+            ocd = parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid SUP '-top' (starts with hypen)" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // invalid separator
+        value = "( 1.1 SUP ( top1 top2 ) )";
+        try
+        {
+            ocd = parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid separator (no DOLLAR)" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // empty sup
+        value = "( 1.1 SUP )";
+        try
+        {
+            ocd = parser.parseObjectClassDescription( value );
+            fail( "Exception expected, no SUP value" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+    }
+
+
+    /**
+     * Tests kind (ABSTRACT, AUXILIARY, STRUCTURAL)
+     * 
+     * @throws ParseException
+     */
+    public void testKind() throws ParseException
+    {
+        String value = null;
+        ObjectClassDescription ocd = null;
+
+        // DEFAULT is STRUCTURAL
+        value = "( 1.1 )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( ObjectClassDescription.Kind.STRUCTURAL, ocd.getKind() );
+
+        // ABSTRACT
+        value = "( 1.1 ABSTRACT )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( ObjectClassDescription.Kind.ABSTRACT, ocd.getKind() );
+
+        // AUXILIARY
+        value = "( 1.1 AUXILIARY )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( ObjectClassDescription.Kind.AUXILIARY, ocd.getKind() );
+
+        // STRUCTURAL
+        value = "( 1.1 STRUCTURAL )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( ObjectClassDescription.Kind.STRUCTURAL, ocd.getKind() );
+
+        // ivalid
+        value = "( 1.1 FOO )";
+        try
+        {
+            ocd = parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid KIND value" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+    }
+
+
+    /**
+     * Test MUST and its values.
+     * Very similar to SUP, so here are less test cases. 
+     * 
+     * @throws ParseException
+     */
+    public void testMust() throws ParseException
+    {
+        String value = null;
+        ObjectClassDescription ocd = null;
+
+        // no MUST
+        value = "( 1.1 )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 0, ocd.getMustAttributeTypes().size() );
+
+        // MUST simple numericoid
+        value = "( 1.1 MUST 1.2.3 )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 1, ocd.getMustAttributeTypes().size() );
+        assertEquals( "1.2.3", ocd.getMustAttributeTypes().get( 0 ) );
+
+        // MUST mulitple
+        value = "(1.1 MUST (cn$sn       $11.22.33.44.55         $  objectClass   ))";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 4, ocd.getMustAttributeTypes().size() );
+        assertEquals( "cn", ocd.getMustAttributeTypes().get( 0 ) );
+        assertEquals( "sn", ocd.getMustAttributeTypes().get( 1 ) );
+        assertEquals( "11.22.33.44.55", ocd.getMustAttributeTypes().get( 2 ) );
+        assertEquals( "objectClass", ocd.getMustAttributeTypes().get( 3 ) );
+
+        // invalid value
+        value = "( 1.1 MUST ( c_n ) )";
+        try
+        {
+            ocd = parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid value c_n" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // no MUST values
+        value = "( 1.1 MUST )";
+        try
+        {
+            ocd = parser.parseObjectClassDescription( value );
+            fail( "Exception expected, no MUST value" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+    }
+
+
+    /**
+     * Test MAY and its values.
+     * Very similar to SUP, so here are less test cases. 
+     * 
+     * @throws ParseException
+     */
+    public void testMay() throws ParseException
+    {
+        String value = null;
+        ObjectClassDescription ocd = null;
+
+        // no MAY
+        value = "( 1.1 )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 0, ocd.getMayAttributeTypes().size() );
+
+        // MAY simple numericoid
+        value = "( 1.1 MAY 1.2.3 )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 1, ocd.getMayAttributeTypes().size() );
+        assertEquals( "1.2.3", ocd.getMayAttributeTypes().get( 0 ) );
+
+        // MAY mulitple
+        value = "(1.1 MAY (cn$sn       $11.22.33.44.55         $  objectClass   ))";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 4, ocd.getMayAttributeTypes().size() );
+        assertEquals( "cn", ocd.getMayAttributeTypes().get( 0 ) );
+        assertEquals( "sn", ocd.getMayAttributeTypes().get( 1 ) );
+        assertEquals( "11.22.33.44.55", ocd.getMayAttributeTypes().get( 2 ) );
+        assertEquals( "objectClass", ocd.getMayAttributeTypes().get( 3 ) );
+
+        // invalid value
+        value = "( 1.1 MAY ( c_n ) )";
+        try
+        {
+            ocd = parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid value c_n" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+    }
+
+
+    /**
+     * Test extensions.
+     * 
+     * @throws ParseException
+     */
+    public void testExtensions() throws ParseException
+    {
+        String value = null;
+        ObjectClassDescription ocd = null;
+
+        // no extension
+        value = "( 1.1 )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 0, ocd.getExtensions().size() );
+
+        // single extension with one value
+        value = "( 1.1 X-TEST 'test' )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 1, ocd.getExtensions().size() );
+        assertNotNull( ocd.getExtensions().get( "X-TEST" ) );
+        assertEquals( 1, ocd.getExtensions().get( "X-TEST" ).size() );
+        assertEquals( "test", ocd.getExtensions().get( "X-TEST" ).get( 0 ) );
+
+        // single extension with multiple values
+        value = "( 1.1 X-TEST-ABC ('test1' 'test äöüß'       'test 部長' ) )";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 1, ocd.getExtensions().size() );
+        assertNotNull( ocd.getExtensions().get( "X-TEST-ABC" ) );
+        assertEquals( 3, ocd.getExtensions().get( "X-TEST-ABC" ).size() );
+        assertEquals( "test1", ocd.getExtensions().get( "X-TEST-ABC" ).get( 0 ) );
+        assertEquals( "test äöüß", ocd.getExtensions().get( "X-TEST-ABC" ).get( 1 ) );
+        assertEquals( "test 部長", ocd.getExtensions().get( "X-TEST-ABC" ).get( 2 ) );
+
+        // multiple extensions
+        value = "(1.1 X-TEST-a ('test1-1' 'test1-2') X-TEST-b ('test2-1' 'test2-2'))";
+        ocd = parser.parseObjectClassDescription( value );
+        assertEquals( 2, ocd.getExtensions().size() );
+        assertNotNull( ocd.getExtensions().get( "X-TEST-a" ) );
+        assertEquals( 2, ocd.getExtensions().get( "X-TEST-a" ).size() );
+        assertEquals( "test1-1", ocd.getExtensions().get( "X-TEST-a" ).get( 0 ) );
+        assertEquals( "test1-2", ocd.getExtensions().get( "X-TEST-a" ).get( 1 ) );
+        assertNotNull( ocd.getExtensions().get( "X-TEST-b" ) );
+        assertEquals( 2, ocd.getExtensions().get( "X-TEST-b" ).size() );
+        assertEquals( "test2-1", ocd.getExtensions().get( "X-TEST-b" ).get( 0 ) );
+        assertEquals( "test2-2", ocd.getExtensions().get( "X-TEST-b" ).get( 1 ) );
+
+        // invalid extension, no number allowed
+        value = "( 1.1 X-TEST1 'test' )";
+        try
+        {
+            ocd = parser.parseObjectClassDescription( value );
+            fail( "Exception expected, invalid extension X-TEST1 (no number allowed)" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+    }
+
+
+    public void testIgnoreElementOrder() throws ParseException
+    {
+        String value = "( 2.5.6.6 STRUCTURAL MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) SUP top DESC 'RFC2256: a person' MUST ( sn $ cn ) NAME 'person' )";
+        ObjectClassDescription ocd = parser.parseObjectClassDescription( value );
+
+        assertEquals( "2.5.6.6", ocd.getNumericOid() );
+        assertEquals( 1, ocd.getNames().size() );
+        assertEquals( "person", ocd.getNames().get( 0 ) );
+        assertEquals( "RFC2256: a person", ocd.getDescription() );
+        assertEquals( 1, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( "top", ocd.getSuperiorObjectClasses().get( 0 ) );
+        assertEquals( ObjectClassDescription.Kind.STRUCTURAL, ocd.getKind() );
+        assertEquals( 2, ocd.getMustAttributeTypes().size() );
+        assertEquals( "sn", ocd.getMustAttributeTypes().get( 0 ) );
+        assertEquals( "cn", ocd.getMustAttributeTypes().get( 1 ) );
+        assertEquals( 4, ocd.getMayAttributeTypes().size() );
+        assertEquals( "userPassword", ocd.getMayAttributeTypes().get( 0 ) );
+        assertEquals( "telephoneNumber", ocd.getMayAttributeTypes().get( 1 ) );
+        assertEquals( "seeAlso", ocd.getMayAttributeTypes().get( 2 ) );
+        assertEquals( "description", ocd.getMayAttributeTypes().get( 3 ) );
+        assertEquals( 0, ocd.getExtensions().size() );
+
+    }
+
+
+    ////////////////////////////////////////////////////////////////
+    //          Some real-world object class definitions          //
+    ////////////////////////////////////////////////////////////////
+
+    public void testRfcTop() throws ParseException
+    {
+        String value = "( 2.5.6.0 NAME 'top' DESC 'top of the superclass chain' ABSTRACT MUST objectClass )";
+        ObjectClassDescription ocd = parser.parseObjectClassDescription( value );
+
+        assertEquals( "2.5.6.0", ocd.getNumericOid() );
+        assertEquals( 1, ocd.getNames().size() );
+        assertEquals( "top", ocd.getNames().get( 0 ) );
+        assertEquals( "top of the superclass chain", ocd.getDescription() );
+        assertEquals( 0, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( ObjectClassDescription.Kind.ABSTRACT, ocd.getKind() );
+        assertEquals( 1, ocd.getMustAttributeTypes().size() );
+        assertEquals( "objectClass", ocd.getMustAttributeTypes().get( 0 ) );
+        assertEquals( 0, ocd.getMayAttributeTypes().size() );
+        assertEquals( 0, ocd.getExtensions().size() );
+    }
+
+
+    public void testRfcPerson() throws ParseException
+    {
+        String value = "( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' SUP top STRUCTURAL MUST ( sn $ cn ) MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) )";
+        ObjectClassDescription ocd = parser.parseObjectClassDescription( value );
+
+        assertEquals( "2.5.6.6", ocd.getNumericOid() );
+        assertEquals( 1, ocd.getNames().size() );
+        assertEquals( "person", ocd.getNames().get( 0 ) );
+        assertEquals( "RFC2256: a person", ocd.getDescription() );
+        assertEquals( 1, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( "top", ocd.getSuperiorObjectClasses().get( 0 ) );
+        assertEquals( ObjectClassDescription.Kind.STRUCTURAL, ocd.getKind() );
+        assertEquals( 2, ocd.getMustAttributeTypes().size() );
+        assertEquals( "sn", ocd.getMustAttributeTypes().get( 0 ) );
+        assertEquals( "cn", ocd.getMustAttributeTypes().get( 1 ) );
+        assertEquals( 4, ocd.getMayAttributeTypes().size() );
+        assertEquals( "userPassword", ocd.getMayAttributeTypes().get( 0 ) );
+        assertEquals( "telephoneNumber", ocd.getMayAttributeTypes().get( 1 ) );
+        assertEquals( "seeAlso", ocd.getMayAttributeTypes().get( 2 ) );
+        assertEquals( "description", ocd.getMayAttributeTypes().get( 3 ) );
+        assertEquals( 0, ocd.getExtensions().size() );
+    }
+
+
+    public void testRfcSimpleSecurityObject() throws ParseException
+    {
+        String value = "( 0.9.2342.19200300.100.4.19 NAME 'simpleSecurityObject' DESC 'RFC1274: simple security object' SUP top AUXILIARY MUST userPassword )";
+        ObjectClassDescription ocd = parser.parseObjectClassDescription( value );
+
+        assertEquals( "0.9.2342.19200300.100.4.19", ocd.getNumericOid() );
+        assertEquals( 1, ocd.getNames().size() );
+        assertEquals( "simpleSecurityObject", ocd.getNames().get( 0 ) );
+        assertEquals( "RFC1274: simple security object", ocd.getDescription() );
+        assertEquals( 1, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( "top", ocd.getSuperiorObjectClasses().get( 0 ) );
+        assertEquals( ObjectClassDescription.Kind.AUXILIARY, ocd.getKind() );
+        assertEquals( 1, ocd.getMustAttributeTypes().size() );
+        assertEquals( "userPassword", ocd.getMustAttributeTypes().get( 0 ) );
+        assertEquals( 0, ocd.getMayAttributeTypes().size() );
+        assertEquals( 0, ocd.getExtensions().size() );
+    }
+
+
+    public void testSunAlias() throws ParseException
+    {
+        String value = "( 2.5.6.1 NAME 'alias' DESC 'Standard LDAP objectclass' SUP top ABSTRACT MUST aliasedObjectName X-ORIGIN 'RFC 2256' )";
+        ObjectClassDescription ocd = parser.parseObjectClassDescription( value );
+
+        assertEquals( "2.5.6.1", ocd.getNumericOid() );
+        assertEquals( 1, ocd.getNames().size() );
+        assertEquals( "alias", ocd.getNames().get( 0 ) );
+        assertEquals( "Standard LDAP objectclass", ocd.getDescription() );
+        assertEquals( 1, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( "top", ocd.getSuperiorObjectClasses().get( 0 ) );
+        assertEquals( ObjectClassDescription.Kind.ABSTRACT, ocd.getKind() );
+        assertEquals( 1, ocd.getMustAttributeTypes().size() );
+        assertEquals( "aliasedObjectName", ocd.getMustAttributeTypes().get( 0 ) );
+        assertEquals( 0, ocd.getMayAttributeTypes().size() );
+
+        assertEquals( 1, ocd.getExtensions().size() );
+        assertNotNull( ocd.getExtensions().get( "X-ORIGIN" ) );
+        assertEquals( 1, ocd.getExtensions().get( "X-ORIGIN" ).size() );
+        assertEquals( "RFC 2256", ocd.getExtensions().get( "X-ORIGIN" ).get( 0 ) );
+    }
+
+
+    public void testNovellDcObject() throws ParseException
+    {
+        String value = "( 1.3.6.1.4.1.1466.344 NAME 'dcObject' AUXILIARY MUST dc X-NDS_NAMING 'dc' X-NDS_NOT_CONTAINER '1' X-NDS_NONREMOVABLE '1' )";
+        ObjectClassDescription ocd = parser.parseObjectClassDescription( value );
+
+        assertEquals( "1.3.6.1.4.1.1466.344", ocd.getNumericOid() );
+        assertEquals( 1, ocd.getNames().size() );
+        assertEquals( "dcObject", ocd.getNames().get( 0 ) );
+        assertEquals( "", ocd.getDescription() );
+        assertEquals( 0, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( ObjectClassDescription.Kind.AUXILIARY, ocd.getKind() );
+        assertEquals( 1, ocd.getMustAttributeTypes().size() );
+        assertEquals( "dc", ocd.getMustAttributeTypes().get( 0 ) );
+        assertEquals( 0, ocd.getMayAttributeTypes().size() );
+
+        assertEquals( 3, ocd.getExtensions().size() );
+        assertNotNull( ocd.getExtensions().get( "X-NDS_NAMING" ) );
+        assertEquals( 1, ocd.getExtensions().get( "X-NDS_NAMING" ).size() );
+        assertEquals( "dc", ocd.getExtensions().get( "X-NDS_NAMING" ).get( 0 ) );
+        assertNotNull( ocd.getExtensions().get( "X-NDS_NOT_CONTAINER" ) );
+        assertEquals( 1, ocd.getExtensions().get( "X-NDS_NOT_CONTAINER" ).size() );
+        assertEquals( "1", ocd.getExtensions().get( "X-NDS_NOT_CONTAINER" ).get( 0 ) );
+        assertNotNull( ocd.getExtensions().get( "X-NDS_NONREMOVABLE" ) );
+        assertEquals( 1, ocd.getExtensions().get( "X-NDS_NONREMOVABLE" ).size() );
+        assertEquals( "1", ocd.getExtensions().get( "X-NDS_NONREMOVABLE" ).get( 0 ) );
+    }
+
+
+    public void testNovellList() throws ParseException
+    {
+        String value = "( 2.16.840.1.113719.1.1.6.1.30 NAME 'List' SUP Top STRUCTURAL MUST cn MAY ( description $ l $ member $ ou $ o $ eMailAddress $ mailboxLocation $ mailboxID $ owner $ seeAlso $ fullName ) X-NDS_NAMING 'cn' X-NDS_CONTAINMENT ( 'Organization' 'organizationalUnit' 'domain' ) X-NDS_NOT_CONTAINER '1' X-NDS_NONREMOVABLE '1' X-NDS_ACL_TEMPLATES '2#entry#[Root Template]#member' )";
+        ObjectClassDescription ocd = parser.parseObjectClassDescription( value );
+
+        assertEquals( "2.16.840.1.113719.1.1.6.1.30", ocd.getNumericOid() );
+        assertEquals( 1, ocd.getNames().size() );
+        assertEquals( "List", ocd.getNames().get( 0 ) );
+        assertEquals( "", ocd.getDescription() );
+        assertEquals( 1, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( "Top", ocd.getSuperiorObjectClasses().get( 0 ) );
+        assertEquals( ObjectClassDescription.Kind.STRUCTURAL, ocd.getKind() );
+        assertEquals( 1, ocd.getMustAttributeTypes().size() );
+        assertEquals( "cn", ocd.getMustAttributeTypes().get( 0 ) );
+        assertEquals( 11, ocd.getMayAttributeTypes().size() );
+        assertEquals( "description", ocd.getMayAttributeTypes().get( 0 ) );
+        assertEquals( "fullName", ocd.getMayAttributeTypes().get( 10 ) );
+
+        assertEquals( 5, ocd.getExtensions().size() );
+        assertNotNull( ocd.getExtensions().get( "X-NDS_NAMING" ) );
+        assertEquals( 1, ocd.getExtensions().get( "X-NDS_NAMING" ).size() );
+        assertEquals( "cn", ocd.getExtensions().get( "X-NDS_NAMING" ).get( 0 ) );
+
+        assertNotNull( ocd.getExtensions().get( "X-NDS_NOT_CONTAINER" ) );
+        assertEquals( 1, ocd.getExtensions().get( "X-NDS_NOT_CONTAINER" ).size() );
+        assertEquals( "1", ocd.getExtensions().get( "X-NDS_NOT_CONTAINER" ).get( 0 ) );
+
+        assertNotNull( ocd.getExtensions().get( "X-NDS_NONREMOVABLE" ) );
+        assertEquals( 1, ocd.getExtensions().get( "X-NDS_NONREMOVABLE" ).size() );
+        assertEquals( "1", ocd.getExtensions().get( "X-NDS_NONREMOVABLE" ).get( 0 ) );
+
+        // X-NDS_CONTAINMENT ( 'Organization' 'organizationalUnit' 'domain' )
+        assertNotNull( ocd.getExtensions().get( "X-NDS_CONTAINMENT" ) );
+        assertEquals( 3, ocd.getExtensions().get( "X-NDS_CONTAINMENT" ).size() );
+        assertEquals( "Organization", ocd.getExtensions().get( "X-NDS_CONTAINMENT" ).get( 0 ) );
+        assertEquals( "organizationalUnit", ocd.getExtensions().get( "X-NDS_CONTAINMENT" ).get( 1 ) );
+        assertEquals( "domain", ocd.getExtensions().get( "X-NDS_CONTAINMENT" ).get( 2 ) );
+
+        // X-NDS_ACL_TEMPLATES '2#entry#[Root Template]#member'
+        assertNotNull( ocd.getExtensions().get( "X-NDS_ACL_TEMPLATES" ) );
+        assertEquals( 1, ocd.getExtensions().get( "X-NDS_ACL_TEMPLATES" ).size() );
+        assertEquals( "2#entry#[Root Template]#member", ocd.getExtensions().get( "X-NDS_ACL_TEMPLATES" ).get( 0 ) );
+    }
+
+
+    public void testMicrosoftAds2000Locality() throws ParseException
+    {
+        String value = "( 2.5.6.3 NAME 'locality' SUP top STRUCTURAL MUST (l ) MAY (st $ street $ searchGuide $ seeAlso ) )";
+        ObjectClassDescription ocd = parser.parseObjectClassDescription( value );
+
+        assertEquals( "2.5.6.3", ocd.getNumericOid() );
+        assertEquals( 1, ocd.getNames().size() );
+        assertEquals( "locality", ocd.getNames().get( 0 ) );
+        assertEquals( "", ocd.getDescription() );
+        assertEquals( 1, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( "top", ocd.getSuperiorObjectClasses().get( 0 ) );
+        assertEquals( ObjectClassDescription.Kind.STRUCTURAL, ocd.getKind() );
+        assertEquals( 1, ocd.getMustAttributeTypes().size() );
+        assertEquals( "l", ocd.getMustAttributeTypes().get( 0 ) );
+        assertEquals( 4, ocd.getMayAttributeTypes().size() );
+        assertEquals( "st", ocd.getMayAttributeTypes().get( 0 ) );
+        assertEquals( "street", ocd.getMayAttributeTypes().get( 1 ) );
+        assertEquals( "searchGuide", ocd.getMayAttributeTypes().get( 2 ) );
+        assertEquals( "seeAlso", ocd.getMayAttributeTypes().get( 3 ) );
+        assertEquals( 0, ocd.getExtensions().size() );
+    }
+
+
+    public void testMicrosoftAds2003Msieee() throws ParseException
+    {
+        String value = "( 1.2.840.113556.1.5.240 NAME 'msieee80211-Policy' SUP top STRUCTURAL MAY (msieee80211-Data $ msieee80211-DataType $ msieee80211-ID ) )";
+        ObjectClassDescription ocd = parser.parseObjectClassDescription( value );
+
+        assertEquals( "1.2.840.113556.1.5.240", ocd.getNumericOid() );
+        assertEquals( 1, ocd.getNames().size() );
+        assertEquals( "msieee80211-Policy", ocd.getNames().get( 0 ) );
+        assertEquals( "", ocd.getDescription() );
+        assertEquals( 1, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( "top", ocd.getSuperiorObjectClasses().get( 0 ) );
+        assertEquals( ObjectClassDescription.Kind.STRUCTURAL, ocd.getKind() );
+        assertEquals( 0, ocd.getMustAttributeTypes().size() );
+        assertEquals( 3, ocd.getMayAttributeTypes().size() );
+        assertEquals( "msieee80211-Data", ocd.getMayAttributeTypes().get( 0 ) );
+        assertEquals( "msieee80211-DataType", ocd.getMayAttributeTypes().get( 1 ) );
+        assertEquals( "msieee80211-ID", ocd.getMayAttributeTypes().get( 2 ) );
+        assertEquals( 0, ocd.getExtensions().size() );
+    }
+
+
+    public void testSiemensDirxX500Subschema() throws ParseException
+    {
+        String value = "( 2.5.20.1 NAME 'x500subSchema' AUXILIARY MAY (dITStructureRules $ nameForms $ dITContentRules $ x500objectClasses $ x500attributeTypes $ matchingRules $ matchingRuleUse) )";
+        ObjectClassDescription ocd = parser.parseObjectClassDescription( value );
+
+        assertEquals( "2.5.20.1", ocd.getNumericOid() );
+        assertEquals( 1, ocd.getNames().size() );
+        assertEquals( "x500subSchema", ocd.getNames().get( 0 ) );
+        assertEquals( "", ocd.getDescription() );
+        assertEquals( 0, ocd.getSuperiorObjectClasses().size() );
+        assertEquals( ObjectClassDescription.Kind.AUXILIARY, ocd.getKind() );
+        assertEquals( 0, ocd.getMustAttributeTypes().size() );
+        assertEquals( 7, ocd.getMayAttributeTypes().size() );
+        assertEquals( "dITStructureRules", ocd.getMayAttributeTypes().get( 0 ) );
+        assertEquals( "matchingRuleUse", ocd.getMayAttributeTypes().get( 6 ) );
+        assertEquals( 0, ocd.getExtensions().size() );
+    }
+
+
+    /**
+     * Tests the multithreaded use of a single parser.
+     */
+    public void testMultiThreaded() throws Exception
+    {
+        // start up and track all threads (40 threads)
+        List<Thread> threads = new ArrayList<Thread>();
+        for ( int ii = 0; ii < 10; ii++ )
+        {
+            Thread t0 = new Thread( new ParseSpecification( "( 1.1 )" ) );
+            Thread t1 = new Thread( new ParseSpecification(
+                "( 2.5.6.0 NAME 'top' DESC 'top of the superclass chain' ABSTRACT MUST objectClass )" ) );
+            Thread t2 = new Thread(
+                new ParseSpecification(
+                    "( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' SUP top STRUCTURAL MUST ( sn $ cn ) MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) )" ) );
+            Thread t3 = new Thread(
+                new ParseSpecification(
+                    "( 2.16.840.1.113719.1.1.6.1.30 NAME 'List' SUP Top STRUCTURAL MUST cn MAY ( description $ l $ member $ ou $ o $ eMailAddress $ mailboxLocation $ mailboxID $ owner $ seeAlso $ fullName ) X-NDS_NAMING 'cn' X-NDS_CONTAINMENT ( 'Organization' 'organizationalUnit' 'domain' ) X-NDS_NOT_CONTAINER '1' X-NDS_NONREMOVABLE '1' X-NDS_ACL_TEMPLATES '2#entry#[Root Template]#member' )" ) );
+            threads.add( t0 );
+            threads.add( t1 );
+            threads.add( t2 );
+            threads.add( t3 );
+            t0.start();
+            t1.start();
+            t2.start();
+            t3.start();
+        }
+
+        // wait until all threads have died
+        boolean hasLiveThreads = false;
+        do
+        {
+            hasLiveThreads = false;
+
+            for ( int ii = 0; ii < threads.size(); ii++ )
+            {
+                Thread t = ( Thread ) threads.get( ii );
+                hasLiveThreads = hasLiveThreads || t.isAlive();
+            }
+        }
+        while ( hasLiveThreads );
+
+        // check that no one thread failed to parse and generate a SS object
+        assertTrue( isSuccessMultithreaded );
+    }
+
+    /**
+     * Used to test multithreaded use of a single parser.
+     */
+    class ParseSpecification implements Runnable
+    {
+        private final String ocd;
+
+        ObjectClassDescription result;
+
+
+        public ParseSpecification( String ocd )
+        {
+            this.ocd = ocd;
+        }
+
+
+        public void run()
+        {
+            try
+            {
+                result = parser.parseObjectClassDescription( ocd );
+            }
+            catch ( ParseException e )
+            {
+                e.printStackTrace();
+            }
+
+            isSuccessMultithreaded = isSuccessMultithreaded && ( result != null );
+        }
+    }
+
+}