From b32944ba175d472abb27afac48b4d6dcff57f511 Mon Sep 17 00:00:00 2001
From: Violette Roche-Montane <violette.roche-montane@forgerock.com>
Date: Fri, 22 Nov 2013 16:57:55 +0000
Subject: [PATCH] CR-2262 SDK Name Form Builder

---
 opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java        |  125 +-
 opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/NameFormTestCase.java     | 1438 +++++++++++++++++++++++++---------------
 opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/NameForm.java             |  344 +++++++++
 opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaElementBuilder.java |  157 ++++
 4 files changed, 1,441 insertions(+), 623 deletions(-)

diff --git a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/NameForm.java b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/NameForm.java
index 0c3cfdf..69da108 100644
--- a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/NameForm.java
+++ b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/NameForm.java
@@ -22,7 +22,7 @@
  *
  *
  *      Copyright 2009 Sun Microsystems, Inc.
- *      Portions copyright 2011 ForgeRock AS
+ *      Portions copyright 2011-2013 ForgeRock AS
  */
 
 package org.forgerock.opendj.ldap.schema;
@@ -32,23 +32,28 @@
 import static com.forgerock.opendj.ldap.CoreMessages.ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_REQUIRED_ATTR1;
 import static com.forgerock.opendj.ldap.CoreMessages.ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_STRUCTURAL_CLASS1;
 
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 import org.forgerock.i18n.LocalizableMessage;
 
-import com.forgerock.opendj.util.Validator;
-
 /**
  * This class defines a data structure for storing and interacting with a name
  * form, which defines the attribute type(s) that must and/or may be used in the
  * RDN of an entry with a given structural objectclass.
  */
 public final class NameForm extends SchemaElement {
+
     // The OID that may be used to reference this definition.
     private final String oid;
 
@@ -74,27 +79,317 @@
     private Set<AttributeType> optionalAttributes = Collections.emptySet();
     private Set<AttributeType> requiredAttributes = Collections.emptySet();
 
-    NameForm(final String oid, final List<String> names, final String description,
-            final boolean obsolete, final String structuralClassOID,
-            final Set<String> requiredAttributeOIDs, final Set<String> optionalAttributeOIDs,
-            final Map<String, List<String>> extraProperties, final String definition) {
-        super(description, extraProperties);
+    /**
+     * The name form builder.
+     */
+    public static class Builder extends SchemaElementBuilder<Builder> {
 
-        Validator.ensureNotNull(oid, names);
-        Validator.ensureNotNull(structuralClassOID, requiredAttributeOIDs, optionalAttributeOIDs);
-        Validator.ensureTrue(requiredAttributeOIDs.size() > 0, "required attribute is empty");
-        this.oid = oid;
-        this.names = names;
-        this.isObsolete = obsolete;
-        this.structuralClassOID = structuralClassOID;
-        this.requiredAttributeOIDs = requiredAttributeOIDs;
-        this.optionalAttributeOIDs = optionalAttributeOIDs;
+        // Required attributes
+        private String oid;
+        private String structuralObjectClassOID;
+        private Set<String> requiredAttribute = new LinkedHashSet<String>();
 
-        if (definition != null) {
-            this.definition = definition;
-        } else {
-            this.definition = buildDefinition();
+        // Optional attributes - initialized to default values.
+        private List<String> names = new LinkedList<String>();
+        private Set<String> optionalAttributes = new LinkedHashSet<String>();
+        private String definition;
+        private boolean isObsolete = false;
+
+        /**
+         * Sets the OID of the name form definition.
+         * <p>
+         * RFC 4512 : numericoid ; object identifier.
+         *
+         * @param oid
+         *            Like 1.3.6.1.4.1.1466.115.121.1.35.
+         * @return This name form builder.
+         */
+        public Builder oid(final String oid) {
+            this.oid = oid;
+            return this;
         }
+
+        /**
+         * Sets the structural object class OID.
+         * <p>
+         * e.g : OC person.
+         *
+         * @param oid
+         *            = SP "OC" SP oid (RFC 4512).
+         * @return This name form builder.
+         */
+        public Builder structuralObjectClassOID(final String oid) {
+            this.structuralObjectClassOID = oid;
+            return this;
+        }
+
+        /**
+         * Sets the user defined names for this definition.
+         * <p>
+         * RFC 4512 : [ SP "NAME" SP qdescrs ] ; short names (descriptors).
+         *
+         * @param names
+         *            Contains a collection of strings.
+         * @return This name form builder.
+         */
+        public Builder names(final Collection<String> names) {
+            this.names.addAll(names);
+            return this;
+        }
+
+        /**
+         * Sets the user defined names for this definition.
+         * <p>
+         * RFC 4512 : [ SP "NAME" SP qdescrs ] ; short names (descriptors).
+         *
+         * @param names
+         *            Contains a series of strings.
+         * @return This name form builder.
+         */
+        public Builder names(final String... names) {
+            return names(Arrays.asList(names));
+        }
+
+        /**
+         * Erases all the names.
+         *
+         * @return This name form builder.
+         */
+        public Builder removeAllNames() {
+            this.names.clear();
+            return this;
+        }
+
+        /**
+         * Removes the defined name.
+         *
+         * @param name
+         *            The name to remove.
+         * @return This name form builder.
+         */
+        public Builder removeName(String name) {
+            names.remove(name);
+            return this;
+        }
+
+        /**
+         * Specifies which attributes are required by this name form.
+         * <p>
+         * RFC 4512 : SP "MUST" SP oids ; attribute types.
+         *
+         * @param oids
+         *            The OIDs of the required attributes.
+         * @return This name form builder.
+         */
+        public Builder requiredAttributes(final String... oids) {
+            return requiredAttributes(Arrays.asList(oids));
+        }
+
+        /**
+         * Specifies which attributes are required by this name form.
+         * <p>
+         * RFC 4512 : SP "MUST" SP oids ; attribute types.
+         *
+         * @param oids
+         *            The OIDs of the required attributes.
+         * @return This name form builder.
+         */
+        public Builder requiredAttributes(final Collection<String> oids) {
+            this.requiredAttribute.addAll(oids);
+            return this;
+        }
+
+        /**
+         * Removes the specified required attribute.
+         *
+         * @param oid
+         *            The OID of the required attributes.
+         * @return This name form builder.
+         */
+        public Builder removeRequiredAttribute(final String oid) {
+            this.requiredAttribute.remove(oid);
+            return this;
+        }
+
+        /**
+         * Removes all the required attributes.
+         *
+         * @return This name form builder.
+         */
+        public Builder removeAllRequiredAttributes() {
+            this.requiredAttribute.clear();
+            return this;
+        }
+
+        /**
+         * Sets the optional attribute OIDs.
+         * <p>
+         * RFC 4512 : [ SP "MAY" SP oids ] ; attribute types.
+         *
+         * @param oids
+         *            The OIDs of the optional attributes.
+         * @return This name form builder.
+         */
+        public Builder optionalAttributes(final String... oids) {
+            return optionalAttributes(Arrays.asList(oids));
+        }
+
+        /**
+         * Sets the optional attributes.
+         * <p>
+         * RFC 4512 : [ SP "MAY" SP oids ] ; attribute types.
+         *
+         * @param oids
+         *            The OIDs of the optional attributes.
+         * @return This name form builder.
+         */
+        public Builder optionalAttributes(final Collection<String> oids) {
+            this.optionalAttributes.addAll(oids);
+            return this;
+        }
+
+        /**
+         * Removes the specified attributes.
+         *
+         * @param oid
+         *            The OID of the optional attributes.
+         * @return This name form builder.
+         */
+        public Builder removeOptionalAttribute(final String oid) {
+            this.optionalAttributes.remove(oid);
+            return this;
+        }
+
+        /**
+         * Removes all the optional attributes.
+         *
+         * @return This name form builder.
+         */
+        public Builder removeAllOptionalAttributes() {
+            this.optionalAttributes.clear();
+            return this;
+        }
+
+        /**
+         * {@code true} if the object class definition is obsolete, otherwise
+         * {@code false}.
+         * <p>
+         * RFC 4512 : [ SP "OBSOLETE" ] ; not active.
+         *
+         * @param isObsolete
+         *            default is {@code false}.
+         * @return This name form builder.
+         */
+        public Builder obsolete(final boolean isObsolete) {
+            this.isObsolete = isObsolete;
+            return this;
+        }
+
+        /**
+         * Sets the definition string used to create this object class.
+         *
+         * @param definition
+         *            The definition to set.
+         * @return This name form builder.
+         */
+        Builder definition(final String definition) {
+            this.definition = definition;
+            return this;
+        }
+
+        /**
+         * Returns the builder.
+         *
+         * @return This name form builder.
+         */
+        @Override
+        Builder getThis() {
+            return this;
+        }
+
+        /**
+         * Creates a new name form builder implementation.
+         *
+         * @param oid
+         *            The OID of the name form definition.
+         * @param builder
+         *            The schema builder linked.
+         */
+        Builder(final String oid, final SchemaBuilder builder) {
+            this.oid(oid);
+            this.schemaBuilder(builder);
+        }
+
+        /**
+         * Duplicates an existing name form builder.
+         *
+         * @param nf
+         *            The name form to duplicate.
+         * @param builder
+         *            The schema builder where to adds this new name form
+         * @throws ConflictingSchemaElementException
+         *             If {@code overwrite} was {@code false} and a conflicting
+         *             schema element was found.
+         */
+        Builder(final NameForm nf, final SchemaBuilder builder) {
+            this.oid = nf.oid;
+            this.definition = nf.buildDefinition();
+            this.description(nf.description);
+            this.structuralObjectClassOID = nf.structuralClassOID;
+            this.isObsolete = nf.isObsolete;
+            this.names = new ArrayList<String>(nf.names);
+            this.extraProperties(new LinkedHashMap<String, List<String>>(nf.extraProperties));
+            this.requiredAttribute = new LinkedHashSet<String>(nf.requiredAttributeOIDs);
+            this.optionalAttributes = new LinkedHashSet<String>(nf.optionalAttributeOIDs);
+            this.schemaBuilder(builder);
+        }
+
+        /**
+         * Adds the name form to the builder overwriting any existing name form
+         * with the same OID.
+         *
+         * @return A schema builder.
+         */
+        public SchemaBuilder addToSchema() {
+            return this.getSchemaBuilder().addNameForm(new NameForm(this), true);
+        }
+
+        /**
+         * Adds the name form to the builder throwing an
+         * ConflictingSchemaElementException if there is an existing name form
+         * with the same OID.
+         *
+         * @return A schema builder.
+         * @throws ConflictingSchemaElementException
+         *             If there is an existing name form with the same OID.
+         */
+        public SchemaBuilder addNoOverwriteToSchema() {
+            return this.getSchemaBuilder().addNameForm(new NameForm(this), false);
+        }
+    }
+
+    private NameForm(final Builder builder) {
+        super(builder.description, builder.extraProperties);
+        // Checks for required attributes.
+        if (builder.oid == null || builder.oid.isEmpty()) {
+            throw new IllegalArgumentException("An OID must be specified.");
+        }
+        if (builder.structuralObjectClassOID == null || builder.structuralObjectClassOID.isEmpty()) {
+            throw new IllegalArgumentException("A structural class OID must be specified.");
+        }
+        if (builder.requiredAttribute == null || builder.requiredAttribute.isEmpty()) {
+            throw new IllegalArgumentException("Required attribute must be specified.");
+        }
+
+        oid = builder.oid;
+        structuralClassOID = builder.structuralObjectClassOID;
+        names = SchemaUtils.unmodifiableCopyOfList(builder.names);
+        requiredAttributeOIDs = SchemaUtils.unmodifiableCopyOfSet(builder.requiredAttribute);
+        optionalAttributeOIDs = SchemaUtils.unmodifiableCopyOfSet(builder.optionalAttributes);
+        isObsolete = builder.isObsolete;
+
+        definition = buildDefinition();
+
     }
 
     /**
@@ -286,11 +581,6 @@
         return definition;
     }
 
-    NameForm duplicate() {
-        return new NameForm(oid, names, description, isObsolete, structuralClassOID,
-                requiredAttributeOIDs, optionalAttributeOIDs, extraProperties, definition);
-    }
-
     @Override
     void toStringContent(final StringBuilder buffer) {
         buffer.append(oid);
diff --git a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java
index b1947b9..6551422 100644
--- a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java
+++ b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java
@@ -41,6 +41,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
@@ -1317,15 +1318,14 @@
             reader.skipWhitespaces();
 
             // The next set of characters must be the OID.
-            final String oid = SchemaUtils.readOID(reader, allowMalformedNamesAndOptions);
+            final NameForm.Builder nameFormBuilder =
+                    new NameForm.Builder(
+                            SchemaUtils.readOID(reader, allowMalformedNamesAndOptions), this);
 
-            List<String> names = Collections.emptyList();
-            String description = "".intern();
-            boolean isObsolete = false;
-            String structuralClass = null;
-            Set<String> optionalAttributes = Collections.emptySet();
-            Set<String> requiredAttributes = null;
-            Map<String, List<String>> extraProperties = Collections.emptyMap();
+
+            // Required properties :
+            String structuralOID = null;
+            Collection<String> requiredAttributes = Collections.emptyList();
 
             // At this point, we should have a pretty specific syntax that
             // describes what may come next, but some of the components are
@@ -1342,35 +1342,37 @@
                     // No more tokens.
                     break;
                 } else if (tokenName.equalsIgnoreCase("name")) {
-                    names = SchemaUtils.readNameDescriptors(reader, allowMalformedNamesAndOptions);
+                    nameFormBuilder.names(SchemaUtils.readNameDescriptors(reader,
+                            allowMalformedNamesAndOptions));
                 } else if (tokenName.equalsIgnoreCase("desc")) {
                     // This specifies the description for the attribute type. It
                     // is an arbitrary string of characters enclosed in single
                     // quotes.
-                    description = SchemaUtils.readQuotedString(reader);
+                    nameFormBuilder.description(SchemaUtils.readQuotedString(reader));
                 } else if (tokenName.equalsIgnoreCase("obsolete")) {
                     // This indicates whether the attribute type should be
                     // considered obsolete. We do not need to do any more
                     // parsing for this token.
-                    isObsolete = true;
+                    nameFormBuilder.obsolete(true);
                 } else if (tokenName.equalsIgnoreCase("oc")) {
-                    structuralClass = SchemaUtils.readOID(reader, allowMalformedNamesAndOptions);
+                    structuralOID = SchemaUtils.readOID(reader, allowMalformedNamesAndOptions);
+                    nameFormBuilder.structuralObjectClassOID(structuralOID);
                 } else if (tokenName.equalsIgnoreCase("must")) {
                     requiredAttributes =
                             SchemaUtils.readOIDs(reader, allowMalformedNamesAndOptions);
+                    nameFormBuilder.requiredAttributes(requiredAttributes);
                 } else if (tokenName.equalsIgnoreCase("may")) {
-                    optionalAttributes =
-                            SchemaUtils.readOIDs(reader, allowMalformedNamesAndOptions);
+                    nameFormBuilder.optionalAttributes(SchemaUtils.readOIDs(reader,
+                            allowMalformedNamesAndOptions));
                 } else if (tokenName.matches("^X-[A-Za-z_-]+$")) {
                     // This must be a non-standard property and it must be
                     // followed by either a single definition in single quotes
                     // or an open parenthesis followed by one or more values in
                     // single quotes separated by spaces followed by a close
                     // parenthesis.
-                    if (extraProperties.isEmpty()) {
-                        extraProperties = new HashMap<String, List<String>>();
-                    }
-                    extraProperties.put(tokenName, SchemaUtils.readExtensions(reader));
+                    final List<String> extensions = SchemaUtils.readExtensions(reader);
+                    nameFormBuilder.extraProperties(tokenName, extensions
+                            .toArray(new String[extensions.size()]));
                 } else {
                     final LocalizableMessage message =
                             ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_TOKEN1.get(definition, tokenName);
@@ -1378,28 +1380,27 @@
                 }
             }
 
+            nameFormBuilder.definition(definition);
+
             // Make sure that a structural class was specified. If not, then
-            // it cannot be valid.
-            if (structuralClass == null) {
+            // it cannot be valid and the name form cannot be build.
+            if (structuralOID == null) {
                 final LocalizableMessage message =
                         ERR_ATTR_SYNTAX_NAME_FORM_NO_STRUCTURAL_CLASS1.get(definition);
                 throw new LocalizedIllegalArgumentException(message);
             }
 
-            if (requiredAttributes == null || requiredAttributes.size() == 0) {
+            if (requiredAttributes.isEmpty()) {
                 final LocalizableMessage message =
                         ERR_ATTR_SYNTAX_NAME_FORM_NO_REQUIRED_ATTR.get(definition);
                 throw new LocalizedIllegalArgumentException(message);
             }
 
-            if (!extraProperties.isEmpty()) {
-                extraProperties = Collections.unmodifiableMap(extraProperties);
+            if (overwrite) {
+                nameFormBuilder.addToSchema();
+            } else {
+                nameFormBuilder.addNoOverwriteToSchema();
             }
-
-            final NameForm nameForm =
-                    new NameForm(oid, names, description, isObsolete, structuralClass,
-                            requiredAttributes, optionalAttributes, extraProperties, definition);
-            addNameForm(nameForm, overwrite);
         } catch (final DecodeException e) {
             final LocalizableMessage msg =
                     ERR_ATTR_SYNTAX_NAME_FORM_INVALID1.get(definition, e.getMessageObject());
@@ -1409,49 +1410,33 @@
     }
 
     /**
-     * Adds the provided name form definition to this schema builder.
+     * Returns a builder which can be used for incrementally constructing a new
+     * name form before adding it to the schema. Example usage:
+     *
+     * <pre>
+     * SchemaBuilder builder = ...;
+     * builder.buildNameForm("1.2.3.4").name("myNameform").addToSchema();
+     * </pre>
      *
      * @param oid
      *            The OID of the name form definition.
-     * @param names
-     *            The user-friendly names of the name form definition.
-     * @param description
-     *            The description of the name form definition.
-     * @param obsolete
-     *            {@code true} if the name form definition is obsolete,
-     *            otherwise {@code false}.
-     * @param structuralClass
-     *            The structural object class this rule applies to.
-     * @param requiredAttributes
-     *            A list of naming attribute types that entries subject to the
-     *            name form must contain.
-     * @param optionalAttributes
-     *            A list of naming attribute types that entries subject to the
-     *            name form may contain.
-     * @param extraProperties
-     *            A map containing additional properties associated with the
-     *            name form definition.
-     * @param overwrite
-     *            {@code true} if any existing name form use with the same OID
-     *            should be overwritten.
-     * @return A reference to this schema builder.
-     * @throws ConflictingSchemaElementException
-     *             If {@code overwrite} was {@code false} and a conflicting
-     *             schema element was found.
+     * @return A builder to continue building the NameForm.
      */
-    SchemaBuilder addNameForm(final String oid, final List<String> names,
-            final String description, final boolean obsolete, final String structuralClass,
-            final Set<String> requiredAttributes, final Set<String> optionalAttributes,
-            final Map<String, List<String>> extraProperties, final boolean overwrite) {
+    public NameForm.Builder buildNameForm(final String oid) {
         lazyInitBuilder();
+        return new NameForm.Builder(oid, this);
+    }
 
-        final NameForm nameForm =
-                new NameForm(oid, unmodifiableCopyOfList(names), description, obsolete,
-                        structuralClass, unmodifiableCopyOfSet(requiredAttributes),
-                        unmodifiableCopyOfSet(optionalAttributes),
-                        unmodifiableCopyOfExtraProperties(extraProperties), null);
-        addNameForm(nameForm, overwrite);
-        return this;
+    /**
+     * Duplicates the name form.
+     *
+     * @param nameForm
+     *            The name form to duplicate.
+     * @return A name form builder.
+     */
+    public NameForm.Builder buildNameForm(final NameForm nameForm) {
+        lazyInitBuilder();
+        return new NameForm.Builder(nameForm, this);
     }
 
     /**
@@ -2664,7 +2649,12 @@
         }
     }
 
-    private void addNameForm(final NameForm form, final boolean overwrite) {
+    SchemaBuilder addNameForm(final NameForm form, final boolean overwrite) {
+        // If schema is not initialized before.
+        if (numericOID2NameForms == null || name2NameForms == null) {
+            lazyInitBuilder();
+        }
+
         NameForm conflictingForm;
         if (numericOID2NameForms.containsKey(form.getOID())) {
             conflictingForm = numericOID2NameForms.get(form.getOID());
@@ -2691,6 +2681,7 @@
                 forms.add(form);
             }
         }
+        return this;
     }
 
     private void addObjectClass(final ObjectClass oc, final boolean overwrite) {
@@ -2748,7 +2739,7 @@
         }
 
         for (final NameForm nameForm : schema.getNameForms()) {
-            addNameForm(nameForm.duplicate(), overwrite);
+            addNameForm(nameForm, overwrite);
         }
 
         for (final DITContentRule contentRule : schema.getDITContentRules()) {
diff --git a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaElementBuilder.java b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaElementBuilder.java
new file mode 100644
index 0000000..c852f8c
--- /dev/null
+++ b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaElementBuilder.java
@@ -0,0 +1,157 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2013 ForgeRock AS
+ */
+package org.forgerock.opendj.ldap.schema;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class defines the set of methods and structures that must be implemented
+ * to define a schema element builder.
+ *
+ * @param <T>
+ *            Builder could be : AttributeTypeBuilder, NameFormBuilder,
+ *            DITContentRuleBuilder, DITStructureRuleBuilder,
+ *            MatchingRuleBuilder, ObjectClassBuilder, ...
+ */
+abstract class SchemaElementBuilder<T extends SchemaElementBuilder<T>> {
+    // List of attributes in common / required attributes used by builders.
+    String description = "";
+    Map<String, List<String>> extraProperties;
+    private SchemaBuilder schemaBuilder = null;
+
+    /**
+     * Creates a new abstract schema element builder implementation.
+     */
+    SchemaElementBuilder() {
+        extraProperties = new LinkedHashMap<String, List<String>>();
+    }
+
+    /**
+     * Defines the schemThe builder.
+     *
+     * @param sc
+     *            The schemThe builder.
+     * @return A builder
+     */
+    T schemaBuilder(final SchemaBuilder sc) {
+        this.schemaBuilder = sc;
+        return getThis();
+    }
+
+    /**
+     * Returns the schemThe builder.
+     *
+     * @return The schemThe builder.
+     */
+    SchemaBuilder getSchemaBuilder() {
+        return schemaBuilder;
+    }
+
+    abstract T getThis();
+
+    /**
+     * The description of the schema element.
+     *
+     * @param description
+     *            a string containing the description of the schema element.
+     * @return <T> The builder.
+     */
+    public T description(final String description) {
+        this.description = description;
+        return getThis();
+    }
+
+    /**
+     * A map containing additional properties associated with the schema element
+     * definition.
+     * <p>
+     * cf. RFC 4512 : extensions WSP RPAREN ; extensions
+     *
+     * @param extraProperties
+     *            Additional properties.
+     * @return The builder.
+     */
+    public T extraProperties(final Map<String, List<String>> extraProperties) {
+        this.extraProperties.putAll(extraProperties);
+        return getThis();
+    }
+
+    /**
+     * Additional properties associated with the schema element definition.
+     * <p>
+     * cf. RFC 4512 : extensions WSP RPAREN ; extensions
+     *
+     * @param key
+     *            like X-ORIGIN
+     * @param extensions
+     *            e.g : 'RFC 2252'
+     * @return The builder.
+     */
+    public T extraProperties(final String key, final String... extensions) {
+        if (this.extraProperties.get(key) != null) {
+            List<String> tempExtraProperties = new ArrayList<String>(this.extraProperties.get(key));
+            tempExtraProperties.addAll(Arrays.asList(extensions));
+            this.extraProperties.put(key, tempExtraProperties);
+        } else {
+            this.extraProperties.put(key, Arrays.asList(extensions));
+        }
+        return getThis();
+    }
+
+    /**
+     * Removes an extra property.
+     *
+     * @param key
+     *            The key to remove.
+     * @param extensions
+     *            The extension to remove. Can be null.
+     * @return The builder.
+     */
+    public T removeExtraProperties(final String key, final String extensions) {
+        if (this.extraProperties.get(key) != null && extensions != null) {
+            List<String> tempExtraProperties = new ArrayList<String>(this.extraProperties.get(key));
+            tempExtraProperties.remove(tempExtraProperties.indexOf(extensions));
+            this.extraProperties.put(key, tempExtraProperties);
+        } else if (this.extraProperties.get(key) != null) {
+            this.extraProperties.remove(key);
+        }
+        return getThis();
+    }
+
+    /**
+     * Clears all extra properties.
+     *
+     * @return The builder.
+     */
+    public T clearExtraProperties() {
+        this.extraProperties.clear();
+        return getThis();
+    }
+}
diff --git a/opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/NameFormTestCase.java b/opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/NameFormTestCase.java
index db135f6..4903fe9 100644
--- a/opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/NameFormTestCase.java
+++ b/opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/NameFormTestCase.java
@@ -21,663 +21,1043 @@
  * CDDL HEADER END
  *
  *
- *      Portions copyright 2012 ForgeRock AS.
+ *      Copyright 2013 ForgeRock AS.
  */
 
 package org.forgerock.opendj.ldap.schema;
 
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.TreeMap;
-import java.util.TreeSet;
 
 import static org.fest.assertions.Assertions.assertThat;
 
-import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.ldap.schema.NameForm.Builder;
 import org.testng.annotations.Test;
 
 /**
- * This class tests the NameForm class.
+ * This class tests the NameForm class. The name form builder can be only used
+ * with the schema builder.
  */
 @SuppressWarnings("javadoc")
 public class NameFormTestCase extends SchemaTestCase {
 
     /**
-     * NameForm doesn't allow null OID.
+     * Creates a new form using the required parameters only (oid, structural
+     * OID and required attributes).
      */
-    @Test(expectedExceptions = NullPointerException.class)
-    public final void testCreateFormDoesntAllowNullOid() {
+    @Test()
+    public final void testCreatesANewFormWithOnlyRequiredParameters() {
 
-        // The set of user defined names for this definition.
-        List<String> names = new ArrayList<String>();
-        names.add("MyNewForm");
-        names.add("TheNewForm");
+        final Schema schema = new SchemaBuilder(Schema.getCoreSchema())
+                .buildNameForm("1.3.6.1.4.1.1466.115.121.1.35")
+                    .structuralObjectClassOID("person")
+                    .requiredAttributes("sn", "cn") // ("cn, sn") is not supported.
+                    .addNoOverwriteToSchema()
+                .toSchema();
 
-        // An optional set of extensions for the name form ( X-ORIGIN / X-SCHEMA-FILE)
-        Map<String, List<String>> extraProperties = new TreeMap<String, List<String>>();
-        List<String> extra = new ArrayList<String>();
-        extra.add("EntrySchemaCheckingTestCase");
-        extraProperties.put("X-ORIGIN", extra);
+        assertThat(schema.getWarnings()).isEmpty();
+        assertThat(schema.getNameForms().size()).isGreaterThan(0);
 
-        // The set of required attribute types for this name form.
-        Set<String> requiredAttributeOIDs = new TreeSet<String>();
-        requiredAttributeOIDs.add("sn");
-        requiredAttributeOIDs.add("cn");
-
-        // @formatter:off
-        new NameForm(null, names, "Description of the new form", false,
-                "mynewform-oid", requiredAttributeOIDs, Collections.<String> emptySet(), extraProperties, null);
-        // @formatter:on
+        for (final NameForm nf : schema.getNameForms()) {
+            assertThat(nf.hasName("hasAName ?")).isFalse();
+            assertThat(nf.getNameOrOID()).isEqualTo("1.3.6.1.4.1.1466.115.121.1.35");
+            assertThat(nf.getOID()).isEqualTo("1.3.6.1.4.1.1466.115.121.1.35");
+            assertThat(nf.toString()).isEqualTo("( 1.3.6.1.4.1.1466.115.121.1.35 OC person MUST ( sn $ cn ) )");
+        }
     }
 
     /**
-     * NameForm doesn't allow null structuralClassOID.
+     * Creates a new form with a name.
      */
-    @Test(expectedExceptions = NullPointerException.class)
-    public final void testCreateFormDoesntAllowNullStructuralClassOID() {
-
-        // The set of user defined names for this definition.
-        List<String> names = new ArrayList<String>();
-        names.add("MyNewForm");
-        names.add("TheNewForm");
-
-        // An optional set of extensions for the name form ( X-ORIGIN / X-SCHEMA-FILE)
-        Map<String, List<String>> extraProperties = new TreeMap<String, List<String>>();
-        List<String> extra = new ArrayList<String>();
-        names.add("EntrySchemaCheckingTestCase");
-        extraProperties.put("X-ORIGIN", extra);
-
-        // The set of required attribute types for this name form.
-        Set<String> requiredAttributeOIDs = new TreeSet<String>();
-        requiredAttributeOIDs.add("sn");
-        requiredAttributeOIDs.add("cn");
+    @Test()
+    public final void testCreatesANewFormWithAName() {
 
         // @formatter:off
-        new NameForm("mynewform-oid", names, "Description of the new form", false,
-                null, requiredAttributeOIDs, Collections.<String> emptySet(), extraProperties, null);
+        final Schema schema = new SchemaBuilder(Schema.getCoreSchema())
+                .buildNameForm("1.3.6.1.4.1.1466.115.121.1.35")
+                    .structuralObjectClassOID("person")
+                    .names("MyNewForm")
+                    .requiredAttributes("sn", "cn")
+                    .addNoOverwriteToSchema()
+                .toSchema();
         // @formatter:on
+
+        assertThat(schema.getWarnings()).isEmpty();
+        assertThat(schema.getNameForms().size()).isGreaterThan(0);
+
+        for (final NameForm nf : schema.getNameForms()) {
+
+            assertThat(nf.hasName("hasAName ?")).isFalse();
+            assertThat(nf.getNameOrOID()).isEqualTo("MyNewForm");
+            assertThat(nf.getOID()).isEqualTo("1.3.6.1.4.1.1466.115.121.1.35");
+
+            assertThat(nf.toString()).isEqualTo(
+                "( 1.3.6.1.4.1.1466.115.121.1.35 NAME 'MyNewForm' OC person MUST ( sn $ cn ) )");
+        }
     }
 
     /**
-     * NameForm doesn't allow null requiredAttributeOIDs.
+     * Creates a new form with optional attributes OID.
      */
-    @Test(expectedExceptions = NullPointerException.class)
-    public final void testCreateFormDoesntAllowNullRequiredAttributeOIDs() {
+    @Test()
+    public final void testCreatesANewFormWithOptionalAttributesOid() {
 
-        // The set of user defined names for this definition.
-        List<String> names = new ArrayList<String>();
-        names.add("MyNewForm");
-        names.add("TheNewForm");
-
-        // An optional set of extensions for the name form ( X-ORIGIN / X-SCHEMA-FILE)
-        Map<String, List<String>> extraProperties = new TreeMap<String, List<String>>();
-        List<String> extra = new ArrayList<String>();
-        extra.add("EntrySchemaCheckingTestCase");
-        extraProperties.put("X-ORIGIN", extra);
-
-        // The set of required attribute types for this name form.
-        Set<String> requiredAttributeOIDs = new TreeSet<String>();
-        requiredAttributeOIDs.add("sn");
-        requiredAttributeOIDs.add("cn");
-
+        final SchemaBuilder sb = new SchemaBuilder();
+        sb.addSchema(Schema.getCoreSchema(), false);
         // @formatter:off
-        new NameForm("mynewform-oid", names, "Description of the new form", false,
-                "mynewform-oid", null, Collections.<String> emptySet(), extraProperties, null);
+        final Schema schema = new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.35")
+                .structuralObjectClassOID("person")
+                .names("MyNewForm")
+                .requiredAttributes("sn", "cn")
+                .optionalAttributes("owner")
+                .addNoOverwriteToSchema()
+            .toSchema();
         // @formatter:on
+
+        assertThat(schema.getWarnings()).isEmpty();
+        assertThat(schema.getNameForms().size()).isGreaterThan(0);
+
+        for (final NameForm nf : schema.getNameForms()) {
+            assertThat(nf.hasName("hasAName ?")).isFalse();
+            assertThat(nf.getNameOrOID()).isEqualTo("MyNewForm");
+            assertThat(nf.getOID()).isEqualTo("1.3.6.1.4.1.1466.115.121.1.35");
+            assertThat(nf.getOptionalAttributes().toString()).contains("owner");
+
+            assertThat(nf.toString()).isEqualTo(
+                    "( 1.3.6.1.4.1.1466.115.121.1.35 NAME 'MyNewForm' OC person MUST ( sn $ cn ) MAY owner )");
+        }
     }
 
     /**
-     * NameForm doesn't allow null requiredAttributeOIDs.
+     * Creates a new form with ExtraProperties.
+     */
+    @Test()
+    public final void testCreatesANewNameFormWithExtraProperties() {
+        // @formatter:off
+        final Schema schema = new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.35")
+                .structuralObjectClassOID("person")
+                .names("MyNewForm")
+                .requiredAttributes("sn", "cn")
+                .optionalAttributes("owner")
+                .extraProperties("X-ORIGIN", "RFC xxx")
+                .addNoOverwriteToSchema()
+            .toSchema();
+        // @formatter:on
+
+        assertThat(schema.getWarnings()).isEmpty();
+        assertThat(schema.getNameForms().size()).isGreaterThan(0);
+
+        for (final NameForm nf : schema.getNameForms()) {
+
+            assertThat(nf.hasName("hasAName ?")).isFalse();
+            assertThat(nf.getNameOrOID()).isEqualTo("MyNewForm");
+            assertThat(nf.getOID()).isEqualTo("1.3.6.1.4.1.1466.115.121.1.35");
+            assertThat(nf.getExtraProperty("X-ORIGIN").get(0)).isEqualTo("RFC xxx");
+
+            assertThat(nf.toString()).isEqualTo(
+                "( 1.3.6.1.4.1.1466.115.121.1.35 NAME 'MyNewForm' OC person "
+                + "MUST ( sn $ cn ) MAY owner X-ORIGIN 'RFC xxx' )");
+        }
+    }
+
+    /**
+     * When required attributes are absents, the builder sends exception. Here,
+     * the OID is missing. An exception is expected.
+     *
+     * @throws SchemaException
      */
     @Test(expectedExceptions = IllegalArgumentException.class)
-    public final void testCreateFormDoesntAllowEmptyRequiredAttributeOIDs() {
-
-        // The set of user defined names for this definition.
-        List<String> names = new ArrayList<String>();
-        names.add("MyNewForm");
-        names.add("TheNewForm");
-
-        // An optional set of extensions for the name form ( X-ORIGIN / X-SCHEMA-FILE)
-        Map<String, List<String>> extraProperties = new TreeMap<String, List<String>>();
-        List<String> extra = new ArrayList<String>();
-        extra.add("EntrySchemaCheckingTestCase");
-        extraProperties.put("X-ORIGIN", extra);
-
-        // The set of required attribute types for this name form.
-        Set<String> requiredAttributeOIDs = new TreeSet<String>();
-
+    public final void testBuilderDoesntAllowNullOid() {
         // @formatter:off
-        new NameForm("mynewform-oid", names, "Description of the new form", false,
-                "mynewform-oid", requiredAttributeOIDs, Collections.<String> emptySet(), extraProperties, null);
+        new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm((String) null)
+                .description("This is a description")
+                .names("name1")
+                .names("name2", "name3")
+                .extraProperties("X-ORIGIN", "NameFormCheckingTestCase")
+                .structuralObjectClassOID("person")
+                .requiredAttributes("sn, cn")
+                .addNoOverwriteToSchema()
+            .toSchema();
         // @formatter:on
     }
 
     /**
-     * NameForm doesn't allow null requiredAttributeOIDs.
+     * When required attributes are absents, the builder sends an exception.
+     * Here, the structural class OID is missing.
+     *
+     * @throws SchemaException
+     */
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public final void testBuilderDoesntAllowNullStructuralClassOid() {
+
+        // @formatter:off
+        new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.35")
+                .description("This is a description")
+                .names("MyNewForm")
+                .extraProperties("X-ORIGIN", "NameFormCheckingTestCase")
+                .requiredAttributes("sn, cn")
+                .addNoOverwriteToSchema()
+            .toSchema();
+        // @formatter:on
+    }
+
+    /**
+     * When required attributes are absents, the builder sends an exception.
+     * Here, the required attributes OID is missing.
+     *
+     * @throws SchemaException
+     */
+    @Test(expectedExceptions = java.lang.IllegalArgumentException.class)
+    public final void testBuilderDoesntAllowEmptyRequiredAttributes() {
+
+        // @formatter:off
+        new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.35")
+                .description("This is a description")
+                .names("MyNewForm")
+                .structuralObjectClassOID("person")
+                .extraProperties("X-ORIGIN", "NameFormCheckingTestCase")
+                .requiredAttributes()
+                .addNoOverwriteToSchema()
+            .toSchema();
+        // @formatter:on
+    }
+
+    /**
+     * When required attributes are absents, the builder sends an exception.
+     * Here, the required attribute is missing.
+     *
+     * @throws SchemaException
+     */
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public final void testBuilderDoesntAllowNullRequiredAttributes() {
+
+        // @formatter:off
+        new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.35")
+                .description("This is a description")
+                .names("MyNewForm")
+                .structuralObjectClassOID("person")
+                .extraProperties("X-ORIGIN", "NameFormCheckingTestCase")
+                .addNoOverwriteToSchema()
+            .toSchema();
+        // @formatter:on
+    }
+
+    /**
+     * Optional attributes shouldn't be equals to null. Exception expected.
+     *
+     * @throws SchemaException
      */
     @Test(expectedExceptions = NullPointerException.class)
-    public final void testCreateFormDoesntAllowNullOptionalAttributeOIDs() {
-
-        // The set of user defined names for this definition.
-        List<String> names = new ArrayList<String>();
-        names.add("MyNewForm");
-        names.add("TheNewForm");
-
-        // An optional set of extensions for the name form ( X-ORIGIN / X-SCHEMA-FILE)
-        Map<String, List<String>> extraProperties = new TreeMap<String, List<String>>();
-        List<String> extra = new ArrayList<String>();
-        extra.add("EntrySchemaCheckingTestCase");
-        extraProperties.put("X-ORIGIN", extra);
-
-        // The set of required attribute types for this name form.
-        Set<String> requiredAttributeOIDs = new TreeSet<String>();
-        requiredAttributeOIDs.add("sn");
-        requiredAttributeOIDs.add("cn");
-
+    public final void testBuilderDoesntAllowNullOptionalAttributes() {
         // @formatter:off
-        new NameForm("mynewform-oid", names, "Description of the new form", false,
-                "mynewform-oid", requiredAttributeOIDs, null, extraProperties, null);
+        new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.35")
+                .description("This is a description")
+                .names("MyNewForm")
+                .extraProperties("X-ORIGIN", "NameFormCheckingTestCase")
+                .structuralObjectClassOID("person")
+                .requiredAttributes("sn, cn")
+                .requiredAttributes((String[]) null)
+                .addNoOverwriteToSchema()
+            .toSchema();
         // @formatter:on
     }
 
     /**
-     * Create a new form and compare the result as string with the expected
-     * usual form.
+     * By default optional attributes are empty.
+     *
+     * @throws SchemaException
      */
     @Test()
-    public final void testCreateNewFormWithUniqueName() {
-
-        // The set of user defined names for this definition.
-        List<String> names = new ArrayList<String>();
-        names.add("MyNewForm");
-
-        // An optional set of extensions for the name form ( X-ORIGIN / X-SCHEMA-FILE)
-        Map<String, List<String>> extraProperties = new TreeMap<String, List<String>>();
-        List<String> extra = new ArrayList<String>();
-        extra.add("EntrySchemaCheckingTestCase");
-        extraProperties.put("X-ORIGIN", extra);
-
-        // The set of required attribute types for this name form.
-        Set<String> requiredAttributeOIDs = new TreeSet<String>();
-        requiredAttributeOIDs.add("sn");
-        requiredAttributeOIDs.add("cn");
+    public final void testBuilderAllowsEmptyOptionalAttributes() {
 
         // @formatter:off
-        NameForm nf = new NameForm("mynewform-oid", names, "Description of the new form", false,
-                "mynewform-oid", requiredAttributeOIDs, Collections.<String> emptySet(), extraProperties, null);
+        new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.35")
+                .description("This is a description")
+                .names("MyNewForm")
+                .structuralObjectClassOID("person")
+                .extraProperties("X-ORIGIN", "NameFormCheckingTestCase")
+                .requiredAttributes("sn", "cn")
+                // .optionalAttributeOIDs("") empty by default.
+                .addNoOverwriteToSchema()
+            .toSchema();
+        // @formatter:on
+    }
 
-        assertThat(nf.hasName("MyNewForm")).isTrue();
-        assertThat(nf.getOID().toString()).isEqualTo("mynewform-oid");
+    /**
+     * Allows removing non-existent attributes without errors.
+     *
+     * @throws SchemaException
+     */
+    @Test()
+    public final void testBuilderAllowRemovingNonexistentAttributes() {
 
+        // @formatter:off
+        final Schema schema = new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.35")
+                .description("This is a description")
+                .names("MyNewForm")
+                .extraProperties("X-ORIGIN", "NameFormCheckingTestCase")
+                .structuralObjectClassOID("person")
+                .requiredAttributes("sn")
+                .removeRequiredAttribute("unknown")
+                .removeOptionalAttribute("optionalunknown")
+                .addNoOverwriteToSchema()
+            .toSchema();
+        // @formatter:on
+
+        assertThat(schema.getWarnings()).isEmpty();
+
+        assertThat(schema.getNameForms()).isNotEmpty();
+        final NameForm nf = schema.getNameForms().iterator().next();
+        assertThat(nf.getOID()).isEqualTo("1.3.6.1.4.1.1466.115.121.1.35");
+        assertThat(nf.getRequiredAttributes().size()).isEqualTo(1);
+        assertThat(nf.getRequiredAttributes().iterator().next().getNameOrOID()).isEqualTo("sn");
+        assertThat(nf.getOptionalAttributes()).isEmpty();
+    }
+
+    /**
+     * Verifying the schema builder allows to add directly a definition. The
+     * name form is created as well.
+     */
+    @Test()
+    public final void testNameFormDefinition() {
+        final SchemaBuilder sb = new SchemaBuilder();
+        sb.addSchema(Schema.getCoreSchema(), false);
+
+        // @formatter:off
+        final String nameFormDefinition = "( 1.3.6.1.4.1.1466.115.121.1.35 NAME 'MyNewForm' "
+                + "DESC 'Description of the new form' "
+                + "OC person MUST ( sn $ cn ) "
+                + "MAY ( description $ uid ) "
+                + "X-SCHEMA-FILE 'NameFormCheckingTestCase' "
+                + "X-ORIGIN 'NameFormCheckingTestCase' )";
+        // @formatter:on
+
+        // Add the nameForm to the schemaBuilder.
+        sb.addNameForm(nameFormDefinition, false);
+        Schema schema = sb.toSchema();
+        assertThat(schema.getWarnings()).isEmpty();
+
+        assertThat(schema.getNameForms()).isNotEmpty();
+        final NameForm nf = schema.getNameForms().iterator().next();
+        assertThat(nf.getOID()).isEqualTo("1.3.6.1.4.1.1466.115.121.1.35");
+        assertThat(nf.getExtraPropertyNames()).isNotEmpty();
+
+        // @formatter:off
         assertThat(nf.toString()).isEqualTo(
-                "( mynewform-oid NAME 'MyNewForm' DESC 'Description of the new form'"
-                + " OC mynewform-oid MUST ( cn $ sn ) X-ORIGIN 'EntrySchemaCheckingTestCase' )");
+                "( 1.3.6.1.4.1.1466.115.121.1.35 NAME 'MyNewForm' "
+                + "DESC 'Description of the new form' "
+                + "OC person MUST ( sn $ cn ) "
+                + "MAY ( description $ uid ) "
+                + "X-SCHEMA-FILE 'NameFormCheckingTestCase' "
+                + "X-ORIGIN 'NameFormCheckingTestCase' )");
         // @formatter:on
     }
 
     /**
-     * Create a new form without name(s).
+     * Required attributes are missing in the following definition.
      */
-    @Test()
-    public final void testCreateNewFormWithOnlyOid() {
-
-        // An optional set of extensions for the name form ( X-ORIGIN / X-SCHEMA-FILE)
-        Map<String, List<String>> extraProperties = new TreeMap<String, List<String>>();
-        List<String> extra = new ArrayList<String>();
-        extra.add("EntrySchemaCheckingTestCase");
-        extraProperties.put("X-ORIGIN", extra);
-
-        // The set of required attribute types for this name form.
-        Set<String> requiredAttributeOIDs = new TreeSet<String>();
-        requiredAttributeOIDs.add("sn");
-        requiredAttributeOIDs.add("cn");
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public final void testNameFormDefinitionDoesntAllowMissingAttributes() {
+        final SchemaBuilder sb = new SchemaBuilder();
+        sb.addSchema(Schema.getCoreSchema(), false);
 
         // @formatter:off
-        NameForm nf = new NameForm("1.3.6.1.4.1.1466.115.121.1.35", new ArrayList<String>(),
-                "Description of the new form", false, "mynewform-oid", requiredAttributeOIDs,
-                Collections.<String> emptySet(), extraProperties, null);
+        final String nameFormDefinition = "( 1.3.6.1.4.1.1466.115.121.1.35 NAME 'MyNewForm' "
+                + "DESC 'Description of the new form' "
+                + "OC person "
+                + "MAY ( description $ uid ) "
+                + "X-SCHEMA-FILE 'NameFormCheckingTestCase' "
+                + "X-ORIGIN 'EntrySchemaCheckingTestCase' "
+                + "X-ORIGIN 'NameFormCheckingTestCase' )";
+        // @formatter:on
 
-        assertThat(nf.hasName("hasAName ?")).isFalse();
-        assertThat(nf.getNameOrOID()).isEqualTo("1.3.6.1.4.1.1466.115.121.1.35");
+        // Add the nameForm to the schemaBuilder.
+        sb.addNameForm(nameFormDefinition, false);
+        final Schema schema = sb.toSchema();
+        assertThat(schema.getWarnings()).isEmpty();
+    }
+
+    /**
+     * Duplicates a name form using the schema builder.
+     *
+     * @throws SchemaException
+     */
+    @Test()
+    public final void testDuplicatesTheNameForm() {
+
+        final SchemaBuilder sb = new SchemaBuilder();
+        sb.addSchema(Schema.getCoreSchema(), false);
+        // @formatter:off
+        final Builder nfb = new Builder("1.3.6.1.4.1.1466.115.121.1.35", sb);
+        nfb.description("Description of the new form")
+            .names("MyNewForm")
+            .structuralObjectClassOID("person")
+            .extraProperties("X-ORIGIN", "NameFormCheckingTestCase")
+            .requiredAttributes("sn", "cn")
+            .optionalAttributes("description", "uid")
+            .addNoOverwriteToSchema();
+
+        Schema schema = sb.toSchema();
+        assertThat(schema.getWarnings()).isEmpty();
+        assertThat(schema.getNameForms()).isNotEmpty();
+        final NameForm nf = schema.getNameForms().iterator().next();
         assertThat(nf.getOID()).isEqualTo("1.3.6.1.4.1.1466.115.121.1.35");
 
-        assertThat(nf.toString()).isEqualTo(
-                "( 1.3.6.1.4.1.1466.115.121.1.35 DESC 'Description of the new form'"
-                + " OC mynewform-oid MUST ( cn $ sn ) X-ORIGIN 'EntrySchemaCheckingTestCase' )");
-        // @formatter:on
+        sb.buildNameForm(nf)
+            .names("Dolly")
+            .oid("1.3.6.1.4.1.1466.115.121.1.36")
+            .addToSchema();
+        schema = sb.toSchema();
+        assertThat(schema.getNameForms()).isNotEmpty();
+        assertThat(schema.getNameForms().size()).isEqualTo(2);
+        assertThat(schema.getWarnings()).isEmpty();
+
+        final Iterator<NameForm> i = schema.getNameForms().iterator();
+        i.next(); // Jump the first element (== nf)
+        final NameForm dolly = i.next(); // Our new cloned NameForm.
+        assertThat(dolly.getOID()).isEqualTo("1.3.6.1.4.1.1466.115.121.1.36"); // With the new OID !
+        assertThat(dolly.getNames().size()).isEqualTo(2);
     }
 
     /**
-     * Create a new form and compare the result as string with the expected
-     * usual form.
-     */
-    @Test()
-    public final void testCreateNewForm() {
-
-        // The set of user defined names for this definition.
-        List<String> names = new ArrayList<String>();
-        names.add("MyNewForm");
-        names.add("TheNewForm");
-
-        // An optional set of extensions for the name form ( X-ORIGIN / X-SCHEMA-FILE)
-        Map<String, List<String>> extraProperties = new TreeMap<String, List<String>>();
-        List<String> extra = new ArrayList<String>();
-        extra.add("EntrySchemaCheckingTestCase");
-        extraProperties.put("X-ORIGIN", extra);
-
-        // The set of required attribute types for this name form.
-        Set<String> requiredAttributeOIDs = new TreeSet<String>();
-        requiredAttributeOIDs.add("sn");
-        requiredAttributeOIDs.add("cn");
-
-        // @formatter:off
-        NameForm nf = new NameForm("mynewform-oid", names, "Description of the new form", false,
-                "mynewform-oid", requiredAttributeOIDs, Collections.<String> emptySet(),
-                extraProperties, null);
-
-        assertThat(nf.toString()).isEqualTo(
-                "( mynewform-oid NAME ( 'MyNewForm' 'TheNewForm' )"
-                + " DESC 'Description of the new form' OC mynewform-oid"
-                + " MUST ( cn $ sn ) X-ORIGIN 'EntrySchemaCheckingTestCase' )");
-        // @formatter:on
-    }
-
-    /**
-     * Create a new form and compare the result as string with the expected
-     * usual form.
-     */
-    @Test()
-    public final void testCreateNewFormWithOptionalAttributesOid() {
-
-        // The set of user defined names for this definition.
-        List<String> names = new ArrayList<String>();
-        names.add("MyNewForm");
-
-        // An optional set of extensions for the name form ( X-ORIGIN / X-SCHEMA-FILE)
-        Map<String, List<String>> extraProperties = new TreeMap<String, List<String>>();
-        List<String> extra = new ArrayList<String>();
-        extra.add("EntrySchemaCheckingTestCase");
-        extraProperties.put("X-ORIGIN", extra);
-
-        // The set of required attribute types for this name form.
-        Set<String> requiredAttributeOIDs = new TreeSet<String>();
-        requiredAttributeOIDs.add("sn");
-        requiredAttributeOIDs.add("cn");
-
-        Set<String> optionalAttributeOIDs = new TreeSet<String>();
-        optionalAttributeOIDs.add("description");
-        optionalAttributeOIDs.add("uid");
-
-        // @formatter:off
-        NameForm nf = new NameForm("mynewform-oid", names, "Description of the new form", false,
-                "mynewform-oid", requiredAttributeOIDs, optionalAttributeOIDs, extraProperties, null);
-
-        assertThat(nf.toString()).isEqualTo(
-                "( mynewform-oid NAME 'MyNewForm' DESC 'Description of the new form'"
-                + " OC mynewform-oid MUST ( cn $ sn )"
-                + " MAY ( description $ uid )"
-                + " X-ORIGIN 'EntrySchemaCheckingTestCase' )");
-        // @formatter:on
-    }
-
-    /**
-     * Adds a new form which is containing an OID not provided by the schema.
-     * Exception expected : The name form description "MyNewForm" is associated
-     * with a structural object class "mynewform-oid" which is not defined in
-     * the schema.
-     *
-     * @throws SchemaException
-     */
-    @Test(expectedExceptions = SchemaException.class)
-    public final void testNameFormValidateDoesntAllowUnknowNewStructuralObject()
-            throws SchemaException {
-
-        // The set of user defined names for this definition.
-        List<String> names = new ArrayList<String>();
-        names.add("MyNewForm");
-        names.add("TheNewForm");
-
-        // An optional set of extensions for the name form ( X-ORIGIN / X-SCHEMA-FILE)
-        Map<String, List<String>> extraProperties = new TreeMap<String, List<String>>();
-        List<String> extra = new ArrayList<String>();
-        extra.add("EntrySchemaCheckingTestCase");
-        extraProperties.put("X-ORIGIN", extra);
-
-        // The set of required attribute types for this name form.
-        Set<String> requiredAttributeOIDs = new TreeSet<String>();
-        requiredAttributeOIDs.add("sn");
-        requiredAttributeOIDs.add("cn");
-
-        Set<String> optionalAttributeOIDs = new TreeSet<String>();
-        optionalAttributeOIDs.add("description");
-        optionalAttributeOIDs.add("uid");
-
-        // @formatter:off
-        NameForm nf1 = new NameForm("mynewform-oid", names, "Description of the new form", false,
-                "mynewform-oid", requiredAttributeOIDs, optionalAttributeOIDs, extraProperties, null);
-
-        assertThat(nf1.toString()).isEqualTo(
-                "( mynewform-oid NAME ( 'MyNewForm' 'TheNewForm' )"
-                + " DESC 'Description of the new form'"
-                + " OC mynewform-oid"
-                + " MUST ( cn $ sn )"
-                + " MAY ( description $ uid )"
-                + " X-ORIGIN 'EntrySchemaCheckingTestCase' )");
-        // @formatter:on
-
-        List<LocalizableMessage> warnings = new ArrayList<LocalizableMessage>();
-        nf1.validate(Schema.getDefaultSchema(), warnings);
-    }
-
-    /**
-     * Validate a nameForm using an abstract object class instead of an
-     * structural object class throws an error.
-     *
-     * @throws SchemaException
-     */
-    @Test(expectedExceptions = SchemaException.class)
-    public final void testNameFormValidateDoesntAllowAbstractObjectClass() throws SchemaException {
-
-        // The set of user defined names for this definition.
-        List<String> names = new ArrayList<String>();
-        names.add("MyNewForm");
-        names.add("TheNewForm");
-
-        // An optional set of extensions for the name form ( X-ORIGIN / X-SCHEMA-FILE)
-        Map<String, List<String>> extraProperties = new TreeMap<String, List<String>>();
-        List<String> extra = new ArrayList<String>();
-        extra.add("EntrySchemaCheckingTestCase");
-        extraProperties.put("X-ORIGIN", extra);
-
-        // The set of required attribute types for this name form.
-        Set<String> requiredAttributeOIDs = new TreeSet<String>();
-        requiredAttributeOIDs.add("sn");
-        requiredAttributeOIDs.add("cn");
-
-        Set<String> optionalAttributeOIDs = new TreeSet<String>();
-        optionalAttributeOIDs.add("description");
-        optionalAttributeOIDs.add("uid");
-
-        // @formatter:off
-        NameForm nf1 = new NameForm("mynewform-oid", names, "Description of the new form", false,
-                "top", requiredAttributeOIDs, optionalAttributeOIDs, extraProperties, null);
-
-        assertThat(nf1.toString()).isEqualTo(
-                "( mynewform-oid NAME ( 'MyNewForm' 'TheNewForm' )"
-                + " DESC 'Description of the new form'"
-                + " OC top"
-                + " MUST ( cn $ sn )"
-                + " MAY ( description $ uid )"
-                + " X-ORIGIN 'EntrySchemaCheckingTestCase' )");
-        // @formatter:on
-
-        List<LocalizableMessage> warnings = new ArrayList<LocalizableMessage>();
-        nf1.validate(Schema.getDefaultSchema(), warnings);
-    }
-
-    /**
-     * Validate a new form without warnings.
+     * Duplicates a name form using the schema builder.
+     * The duplicate name form contains an inappropriate structural class OID which made the build fails.
+     * <p>Warning from schema is : <pre>
+     * "The name form description "MyNewForm" is associated with a structural object class
+     * "wrongStructuralOID" which is not defined in the schema".</pre>
      *
      * @throws SchemaException
      */
     @Test()
-    public final void testNameFormValidate() throws SchemaException {
+    public final void testDuplicatesTheNameFormFails() {
 
-        // The set of user defined names for this definition.
-        List<String> names = new ArrayList<String>();
-        names.add("MyNewForm");
-        names.add("TheNewForm");
-
-        // An optional set of extensions for the name form ( X-ORIGIN / X-SCHEMA-FILE)
-        Map<String, List<String>> extraProperties = new TreeMap<String, List<String>>();
-        List<String> extra = new ArrayList<String>();
-        extra.add("EntrySchemaCheckingTestCase");
-        extraProperties.put("X-ORIGIN", extra);
-
-        // The set of required attribute types for this name form.
-        Set<String> requiredAttributeOIDs = new TreeSet<String>();
-        requiredAttributeOIDs.add("sn");
-        requiredAttributeOIDs.add("cn");
-
-        Set<String> optionalAttributeOIDs = new TreeSet<String>();
-        optionalAttributeOIDs.add("description");
-        optionalAttributeOIDs.add("uid");
-
+        final SchemaBuilder sb = new SchemaBuilder();
+        sb.addSchema(Schema.getCoreSchema(), false);
         // @formatter:off
-        NameForm nf1 = new NameForm("1.3.6.1.4.1.1466.115.121.1.35", names, "Description of the new form", false,
-                "person", requiredAttributeOIDs, optionalAttributeOIDs, extraProperties, null);
+        final Builder nfb = new Builder("1.3.6.1.4.1.1466.115.121.1.35", sb);
+        nfb.description("Description of the new form")
+            .names("MyNewForm")
+            .structuralObjectClassOID("person")
+            .extraProperties("X-ORIGIN", "NameFormCheckingTestCase")
+            .requiredAttributes("sn", "cn")
+            .optionalAttributes("description", "uid")
+            .addNoOverwriteToSchema();
 
-        assertThat(nf1.toString()).isEqualTo(
-                "( 1.3.6.1.4.1.1466.115.121.1.35 NAME ( 'MyNewForm' 'TheNewForm' )"
-                + " DESC 'Description of the new form'"
-                // Structural Object class, contained in the core schema:
-                + " OC person"
-                + " MUST ( cn $ sn )"
-                + " MAY ( description $ uid )"
-                + " X-ORIGIN 'EntrySchemaCheckingTestCase' )");
-        // @formatter:on
+        Schema schema = sb.toSchema();
+        assertThat(schema.getWarnings()).isEmpty();
+        assertThat(schema.getNameForms()).isNotEmpty();
+        final NameForm nf = schema.getNameForms().iterator().next();
+        assertThat(nf.getOID()).isEqualTo("1.3.6.1.4.1.1466.115.121.1.35");
 
-        List<LocalizableMessage> warnings = new ArrayList<LocalizableMessage>();
-        nf1.validate(Schema.getCoreSchema(), warnings);
-
-        assertThat(warnings).isEmpty();
+        sb.buildNameForm(nf)
+            .names("Dolly")
+            .oid("1.3.6.1.4.1.1466.115.121.1.36")
+            .structuralObjectClassOID("wrongStructuralOID")
+            .addToSchema();
+        schema = sb.toSchema();
+        assertThat(schema.getNameForms().size()).isEqualTo(1); // MyNewForm
+        // The duplicate name form is  not created and the schema contains warnings about.
+        assertThat(schema.getWarnings()).isNotEmpty();
     }
 
     /**
-     * Compare two same nameForm using the equal function.
+     * Compare two same name forms using the equal function.
      */
     @Test()
     public final void testNameFormEqualsTrue() {
 
-        // The set of user defined names for this definition.
-        List<String> names = new ArrayList<String>();
-        names.add("MyNewForm");
-        names.add("TheNewForm");
-
-        // An optional set of extensions for the name form ( X-ORIGIN / X-SCHEMA-FILE)
-        Map<String, List<String>> extraProperties = new TreeMap<String, List<String>>();
-        List<String> extra = new ArrayList<String>();
-        extra.add("EntrySchemaCheckingTestCase");
-        extraProperties.put("X-ORIGIN", extra);
-
-        // The set of required attribute types for this name form.
-        Set<String> requiredAttributeOIDs = new TreeSet<String>();
-        requiredAttributeOIDs.add("sn");
-        requiredAttributeOIDs.add("cn");
-
         // @formatter:off
-        NameForm nf1 = new NameForm("mynewform-oid", names, "Description of the new form", false,
-                "mynewform-oid", requiredAttributeOIDs, Collections.<String> emptySet(), extraProperties, null);
-
-        NameForm nf2 = new NameForm("mynewform-oid", names, "Description of the new form", false,
-                "mynewform-oid", requiredAttributeOIDs, Collections.<String> emptySet(), extraProperties, null);
+        final Schema schema = new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.35")
+                .description("Description of the new form")
+                .names("MyNewForm")
+                .names("TheNewForm")
+                .structuralObjectClassOID("person")
+                .extraProperties("X-ORIGIN", "NameFormCheckingTestCase")
+                .requiredAttributes("sn", "cn")
+                .optionalAttributes("description", "uid")
+                .addNoOverwriteToSchema()
+            .toSchema();
         // @formatter:on
 
+        final NameForm nf1 = schema.getNameForms().iterator().next();
+
+        final Schema schema2 = new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.35")
+            .names("MyNewForm")
+            .structuralObjectClassOID("person")
+            .requiredAttributes("sn", "cn")
+            .addNoOverwriteToSchema().toSchema();
+        final NameForm nf2 = schema2.getNameForm("MyNewForm");
+
         assertThat(nf1.equals(nf2)).isTrue();
     }
 
     /**
-     * Equals between two 'nameforms' fails.
+     * Equals between two name forms fails.
      */
     @Test()
     public final void testNameFormEqualsFalse() {
 
-        // The set of user defined names for this definition.
-        List<String> names = new ArrayList<String>();
-        names.add("MyNewForm");
-        names.add("TheNewForm");
-
-        // An optional set of extensions for the name form ( X-ORIGIN / X-SCHEMA-FILE)
-        Map<String, List<String>> extraProperties = new TreeMap<String, List<String>>();
-        List<String> extra = new ArrayList<String>();
-        extra.add("EntrySchemaCheckingTestCase");
-        extraProperties.put("X-ORIGIN", extra);
-
-        // The set of required attribute types for this name form.
-        Set<String> requiredAttributeOIDs = new TreeSet<String>();
-        requiredAttributeOIDs.add("sn");
-        requiredAttributeOIDs.add("cn");
+        // @formatter:off
+        final Schema schema = new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.35")
+                .description("Description of the new form")
+                .names("MyNewForm")
+                .names("TheNewForm")
+                .structuralObjectClassOID("person")
+                .extraProperties("X-ORIGIN", "NameFormCheckingTestCase")
+                .requiredAttributes("sn", "cn")
+                .optionalAttributes("description", "uid")
+                .addNoOverwriteToSchema()
+            .toSchema();
+        // @formatter:on
+        final NameForm nf1 = schema.getNameForms().iterator().next();
 
         // @formatter:off
-        NameForm nf1 = new NameForm("mynewform-oid", names, "Description of the new form", false,
-                "mynewform-oid", requiredAttributeOIDs, Collections.<String> emptySet(), extraProperties, null);
-
-        NameForm nf2 = new NameForm("mynewform-oid2", names, "Description of the new form", false,
-                "mynewform-oid", requiredAttributeOIDs, Collections.<String> emptySet(), extraProperties, null);
+        final Schema schema2 = new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.36")
+                .description("Description of the new form")
+                .names("MyNewForm")
+                .names("TheNewForm")
+                .structuralObjectClassOID("person")
+                .extraProperties("X-ORIGIN", "NameFormCheckingTestCase")
+                .requiredAttributes("sn", "cn")
+                .optionalAttributes("description", "uid")
+                .addNoOverwriteToSchema()
+            .toSchema();
         // @formatter:on
 
-        assertThat(nf1.getOID()).isEqualTo("mynewform-oid");
-        assertThat(nf2.getOID()).isEqualTo("mynewform-oid2");
-        // fails if oid is different.
-        assertThat(nf1.equals(nf2)).isFalse();
+        assertThat(nf1.equals(schema2.getNameForms().iterator().next())).isFalse();
     }
 
     /**
-     * Duplicating a form without validating it doesn't copy OptionalAttributes
-     * and RequiredAttributes.
-     */
-    @Test()
-    public final void testNameFormDuplicateDoesntDuplicateAllAttributeWithoutValidateIt() {
-
-        // The set of user defined names for this definition.
-        List<String> names = new ArrayList<String>();
-        names.add("MyNewForm");
-        names.add("TheNewForm");
-
-        // An optional set of extensions for the name form ( X-ORIGIN / X-SCHEMA-FILE)
-        Map<String, List<String>> extraProperties = new TreeMap<String, List<String>>();
-        List<String> extra = new ArrayList<String>();
-        extra.add("EntrySchemaCheckingTestCase");
-        extraProperties.put("X-ORIGIN", extra);
-
-        // The set of required attribute types for this name form.
-        Set<String> requiredAttributeOIDs = new TreeSet<String>();
-        requiredAttributeOIDs.add("sn");
-        requiredAttributeOIDs.add("cn");
-
-        // The set of optional attribute types
-        Set<String> optionalAttributeOIDs = new TreeSet<String>();
-        optionalAttributeOIDs.add("description");
-        optionalAttributeOIDs.add("uid");
-
-        // @formatter:off
-        NameForm nf1 = new NameForm("1.3.6.1.4.1.1466.115.121.1.35", names, "Description of the new form", false,
-                "person", requiredAttributeOIDs, optionalAttributeOIDs, extraProperties, null);
-
-        assertThat(nf1.toString()).isEqualTo(
-                "( 1.3.6.1.4.1.1466.115.121.1.35 NAME ( 'MyNewForm' 'TheNewForm' )"
-                + " DESC 'Description of the new form'"
-                // Structural Object class, contained in the core schema:
-                + " OC person"
-                + " MUST ( cn $ sn )"
-                + " MAY ( description $ uid )"
-                + " X-ORIGIN 'EntrySchemaCheckingTestCase' )");
-        // @formatter:on
-
-        // Duplicating the 'nameform'.
-        NameForm nf2 = nf1.duplicate();
-
-        // Checking if the attributes are the same :
-        assertThat(nf2.getDescription()).isEqualTo(nf1.getDescription());
-        assertThat(nf2.getDescription()).isEqualTo("Description of the new form");
-
-        assertThat(nf2.getExtraPropertyNames()).isEqualTo(nf1.getExtraPropertyNames());
-        assertThat(nf2.getExtraPropertyNames().iterator().next()).isEqualTo("X-ORIGIN");
-
-        assertThat(nf2.getNameOrOID()).isEqualTo(nf1.getNameOrOID());
-        assertThat(nf2.getNameOrOID()).isEqualTo("MyNewForm");
-
-        assertThat(nf2.getOID()).isEqualTo(nf1.getOID());
-        assertThat(nf2.getOID()).isEqualTo("1.3.6.1.4.1.1466.115.121.1.35");
-
-        // Required and optional attributes are empty.
-        assertThat(nf2.getOptionalAttributes()).isEmpty();
-        assertThat(nf2.getOptionalAttributes()).isEqualTo(nf1.getOptionalAttributes());
-
-        assertThat(nf2.getRequiredAttributes()).isEmpty();
-        assertThat(nf2.getRequiredAttributes()).isEqualTo(nf1.getRequiredAttributes());
-
-        assertThat(nf2.getStructuralClass()).isEqualTo(nf1.getStructuralClass());
-    }
-
-    /**
-     * Duplicating a form succeeds after a schema validation.
+     * Testing to add a name form using the definition.
      *
      * @throws SchemaException
      */
     @Test()
-    public final void testNameFormDuplicateSucceedAfterValidation() throws SchemaException {
+    public final void testCreateFormUsingDefinitionAndSchemaBuilder() {
+        final SchemaBuilder sb = new SchemaBuilder();
 
-        // The set of user defined names for this definition.
-        List<String> names = new ArrayList<String>();
-        names.add("MyNewForm");
-        names.add("TheNewForm");
+        // @formatter:off
+        sb.addSchema(Schema.getCoreSchema(), false)
+            .addObjectClass(
+                "( mycustomobjectclass-oid NAME 'myCustomObjectClassOC' SUP top "
+                + "STRUCTURAL MUST cn X-ORIGIN 'NameFormTestCase')", false)
+            .addNameForm(
+                "( mycustomnameform-oid NAME 'myCustomNameForm' OC myCustomObjectClassOC "
+                + "MUST cn X-ORIGIN 'NameFormTestCase' )",
+                false)
+            .toSchema();
+        // @formatter:on
 
-        // An optional set of extensions for the name form ( X-ORIGIN / X-SCHEMA-FILE)
-        Map<String, List<String>> extraProperties = new TreeMap<String, List<String>>();
-        List<String> extra = new ArrayList<String>();
+        final Schema schema = sb.toSchema();
+        assertThat(schema.getWarnings()).isEmpty();
+        assertThat(schema.getNameFormsWithName("mycustomnameform")).isNotNull();
+        for (final NameForm o : schema.getNameForms()) {
+            assertThat(o.getNameOrOID()).isEqualTo("myCustomNameForm");
+            assertThat(o.getOID()).isEqualTo("mycustomnameform-oid");
+            assertThat(o.getStructuralClass().getOID().toString()).isEqualTo(
+                    "mycustomobjectclass-oid");
+        }
+    }
+
+    /**
+     * Compare two same name forms using the equal function. One created by the
+     * name form builder, the other by the schema builder directly using the
+     * definition.
+     */
+    @Test()
+    public final void testNameFormEqualityReturnsTrueBetweenBuilderAndDefinition() {
+
+        // @formatter:off
+        final Schema schema = new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.35")
+                .description("Description of the new form")
+                .names("MyNewForm")
+                .structuralObjectClassOID("person")
+                .extraProperties("X-ORIGIN", "NameFormCheckingTestCase")
+                .requiredAttributes("sn", "cn")
+                .optionalAttributes("description", "uid")
+                .addNoOverwriteToSchema()
+            .toSchema();
+        // @formatter:on
+
+        assertThat(schema.getWarnings()).isEmpty();
+        final NameForm nf1 = schema.getNameForms().iterator().next();
+
+        final SchemaBuilder sb2 = new SchemaBuilder();
+        sb2.addSchema(Schema.getCoreSchema(), false);
+
+        // @formatter:off
+        sb2.addNameForm(
+                "( 1.3.6.1.4.1.1466.115.121.1.35 NAME ( 'MyNewForm' ) "
+                + "DESC 'Description of the new form' "
+                + "OC person MUST ( sn $ cn ) "
+                + "MAY ( description $ uid ) "
+                + "X-ORIGIN 'NameFormCheckingTestCase' )", false);
+        // @formatter:on
+
+        final NameForm nf2 = sb2.toSchema().getNameForm("MyNewForm");
+
+        assertThat(nf1.equals(nf2)).isTrue();
+    }
+
+    /**
+     * Compare two same name forms using the equal function. One created by the
+     * name form builder, the other by the schema builder directly using the
+     * definition with different OID.
+     */
+    @Test()
+    public final void testNameFormEqualityReturnsFalseBetweenBuilderAndDefinition() {
+
+        // @formatter:off
+        final Schema schema = new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.35")
+                .description("Description of the new form")
+                .names("MyNewForm")
+                .structuralObjectClassOID("person")
+                .extraProperties("X-ORIGIN", "NameFormCheckingTestCase")
+                .requiredAttributes("sn", "cn")
+                .optionalAttributes("description", "uid")
+                .addNoOverwriteToSchema()
+            .toSchema();
+        // @formatter:on
+
+        assertThat(schema.getWarnings()).isEmpty();
+        final NameForm nf1 = schema.getNameForms().iterator().next();
+
+        final SchemaBuilder sb2 = new SchemaBuilder();
+        sb2.addSchema(Schema.getCoreSchema(), false);
+
+        // @formatter:off
+        sb2.addNameForm(
+                "( 1.3.6.1.4.1.1466.115.121.1.36 NAME ( 'MyNewForm' ) " // OID changed.
+                + "DESC 'Description of the new form' "
+                + "OC person MUST ( sn $ cn ) "
+                + "MAY ( description $ uid ) "
+                + "X-ORIGIN 'NameFormCheckingTestCase' )", false);
+        // @formatter:on
+
+        final NameForm nf2 = sb2.toSchema().getNameForm("MyNewForm");
+        // Equals is only based on the OID.
+        assertThat(nf1.equals(nf2)).isFalse();
+    }
+
+    /**
+     * Validates a name form using an abstract object class instead of an
+     * structural object class and throws an error.
+     *
+     * @throws SchemaException
+     */
+    @Test()
+    public final void testNameFormValidateDoesntAllowAbstractObjectClass() {
+
+        // @formatter:off
+        final Schema schema = new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("mynewform-oid")
+                .description("Description of the new form")
+                .names("MyNewForm")
+                .structuralObjectClassOID("top")
+                .extraProperties("X-ORIGIN", "NameFormCheckingTestCase")
+                .requiredAttributes("sn", "cn")
+                .optionalAttributes("description", "uid")
+                .addNoOverwriteToSchema()
+            .toSchema();
+        // @formatter:on
+
+        assertThat(schema.getNameForms()).isEmpty();
+        assertThat(schema.getWarnings()).isNotEmpty();
+        assertThat(schema.getWarnings().toString()).contains(
+                "This object class exists in the schema but is defined as ABSTRACT rather than structural");
+        // output is : The name form description "MyNewForm" is associated with the "top" object class.
+        // This object class exists in the schema but is defined as ABSTRACT rather than structural
+    }
+
+    /**
+     * Creates a name form using the appropriate structural object class.
+     *
+     * @throws SchemaException
+     */
+    @Test()
+    public final void testNameFormValidateAllowsStructuralObjectClass() {
+
+        // @formatter:off
+        final Schema schema = new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("mynewform-oid")
+                .description("Description of the new form")
+                .names("MyNewForm")
+                .structuralObjectClassOID("person")
+                .extraProperties("X-ORIGIN", "NameFormCheckingTestCase")
+                .requiredAttributes("sn", "cn")
+                .optionalAttributes("description", "uid")
+                .addNoOverwriteToSchema()
+            .toSchema();
+        // @formatter:on
+
+        assertThat(schema.getNameForms()).isNotEmpty();
+        assertThat(schema.getWarnings()).isEmpty();
+
+        assertThat(schema.getNameForms().iterator().next().getOID()).isEqualTo("mynewform-oid");
+        assertThat(schema.getNameForms().iterator().next().getNames().get(0)).isEqualTo("MyNewForm");
+    }
+
+    /**
+     * Adds multiple attributes... e.g : name form containing multiple
+     * extra-properties, requiredAttributes, optional attributes, names...
+     *
+     * @throws SchemaException
+     */
+    @Test()
+    public final void testBuildsANewFormWithMultipleAttributes() {
+
+        // @formatter:off
+        final Schema schema = new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("0.0.1.2.3")
+                .description("multipleAttributes Test description")
+                .names("multipleAttributes")
+                .structuralObjectClassOID("person")
+                .extraProperties("X-ORIGIN", "NameFormCheckingTestCase")
+                .extraProperties("X-ORIGIN", "NameFormCheckingTestCase2")
+                .requiredAttributes("sn", "cn") // ("cn, sn") is not supported.
+                .requiredAttributes("uid")
+                .optionalAttributes("owner")
+                .optionalAttributes("l")
+                .names("Rock")
+                .addNoOverwriteToSchema()
+            .toSchema();
+        // @formatter:on
+
+        assertThat(schema.getWarnings()).isEmpty();
+        assertThat(schema.getNameForms()).isNotEmpty();
+
+        for (final NameForm nf : schema.getNameForms()) {
+
+            assertThat(nf.getDescription()).isEqualTo("multipleAttributes Test description");
+            assertThat(nf.getOID()).isEqualTo("0.0.1.2.3");
+
+            assertThat(nf.getNames().get(0)).isEqualTo("multipleAttributes");
+            assertThat(nf.getNames().get(1)).isEqualTo("Rock");
+            assertThat(nf.getExtraProperty("X-ORIGIN").get(0))
+                    .isEqualTo("NameFormCheckingTestCase");
+            assertThat(nf.getExtraProperty("X-ORIGIN").get(1)).isEqualTo(
+                    "NameFormCheckingTestCase2");
+
+            assertThat(nf.getStructuralClass().getNameOrOID()).isEqualTo("person");
+
+            // RequiredAttributes is accessible only after validate
+            for (final AttributeType att : nf.getRequiredAttributes()) {
+                assertThat(
+                        att.getNameOrOID().contains("cn") || att.getNameOrOID().contains("sn")
+                                || att.getNameOrOID().contains("uid")).isTrue();
+            }
+            // OptionalAttributes is accessible only after validate
+            for (final AttributeType att : nf.getOptionalAttributes()) {
+                assertThat(att.getNameOrOID().contains("owner") || att.getNameOrOID().contains("l"))
+                        .isTrue();
+            }
+        }
+    }
+
+    /**
+     * Using the schema builder for adding new name forms. Allows methods
+     * chaining.
+     * <p>
+     * e.g : (SchemaBuilder) <code>
+     * scb.addNameForm("1.2.3").build(true).addAttributeType
+     * (...).build(false).addNameForm(...)...etc.
+     * </code>
+     * <p>
+     * N.B : NameForm is validated when the SchemaBuilder is building a Schema.
+     * If the NameForm is not valid, the SchemaBuilder just remove the invalid
+     * NameForm.
+     *
+     * @throws SchemaException
+     */
+    @Test()
+    public final void testCreatesANewFormUsingChainingMethods() {
+        final Map<String, List<String>> extraProperties = new TreeMap<String, List<String>>();
+        final List<String> extra = new ArrayList<String>();
         extra.add("EntrySchemaCheckingTestCase");
         extraProperties.put("X-ORIGIN", extra);
 
-        // The set of required attribute types for this name form.
-        Set<String> requiredAttributeOIDs = new TreeSet<String>();
-        requiredAttributeOIDs.add("sn");
-        requiredAttributeOIDs.add("cn");
-
-        Set<String> optionalAttributeOIDs = new TreeSet<String>();
-        optionalAttributeOIDs.add("description");
-        optionalAttributeOIDs.add("uid");
-
         // @formatter:off
-        NameForm nf1 = new NameForm("1.3.6.1.4.1.1466.115.121.1.35", names, "Description of the new form", false,
-                "person", requiredAttributeOIDs, optionalAttributeOIDs, extraProperties, null);
-
-        assertThat(nf1.toString()).isEqualTo(
-                "( 1.3.6.1.4.1.1466.115.121.1.35 NAME ( 'MyNewForm' 'TheNewForm' )"
-                + " DESC 'Description of the new form'"
-                // Structural Object class, contained in the core schema:
-                + " OC person"
-                + " MUST ( cn $ sn )"
-                + " MAY ( description $ uid )"
-                + " X-ORIGIN 'EntrySchemaCheckingTestCase' )");
+        final Schema schema = new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("1.2.3")
+                .description("NF1's description")
+                .names("theFirstNameForm")
+                .structuralObjectClassOID("person")
+                .extraProperties(extraProperties)
+                .requiredAttributes("uid")
+                .optionalAttributes("sn")
+                .addToSchema()
+            .buildNameForm("4.4.4")
+                .description("NF2's description")
+                .names("theSecondNameForm")
+                .structuralObjectClassOID("person")
+                .extraProperties(extraProperties)
+                .requiredAttributes("uid")
+                .requiredAttributes("sn")
+                .addToSchema()
+            .toSchema();
         // @formatter:on
 
-        List<LocalizableMessage> warnings1 = new ArrayList<LocalizableMessage>();
-        nf1.validate(Schema.getCoreSchema(), warnings1);
+        // First name form
+        final NameForm first = schema.getNameForm("theFirstNameForm");
+        assertThat(first.getOID()).isEqualTo("1.2.3");
+        assertThat(first.getDescription()).isEqualTo("NF1's description");
+        assertThat(first.getRequiredAttributes()).isNotEmpty();
+        assertThat(first.getOptionalAttributes()).isNotEmpty();
+        assertThat(first.getStructuralClass().getNameOrOID()).isEqualTo("person");
 
-        // Duplicating the 'nameform'.
-        NameForm nf2 = nf1.duplicate();
+        // Second name form
+        final NameForm second = schema.getNameForm("theSecondNameForm");
+        assertThat(second.getOID()).isEqualTo("4.4.4");
+        assertThat(second.getDescription()).isEqualTo("NF2's description");
+        assertThat(second.getRequiredAttributes()).isNotEmpty();
+        assertThat(second.getOptionalAttributes()).isEmpty();
+    }
 
-        // Required and optional attributes are empty :
-        assertThat(nf2.getOptionalAttributes()).isEmpty();
-        assertThat(nf2.getRequiredAttributes()).isEmpty();
+    /**
+     * Remove functions uses on names / required attribute /
+     *
+     * @throws SchemaException
+     */
+    @Test()
+    public final void testCreatesNewFormAndRemovesAttributes() {
 
-        List<LocalizableMessage> warnings2 = new ArrayList<LocalizableMessage>();
-        nf2.validate(Schema.getCoreSchema(), warnings2);
+        // @formatter:off
+        final Schema schema = new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("0.0.1.2.3")
+                .description("multipleAttributes Test description")
+                .structuralObjectClassOID("person")
+                .extraProperties("X-ORIGIN", "NameFormCheckingTestCase")
+                .extraProperties("X-ORIGIN", "NameFormCheckingTestCase2")
+                .requiredAttributes("sn", "cn")
+                .requiredAttributes("uid")
+                .optionalAttributes("givenName")
+                .optionalAttributes("l")
+                .names("nameform1")
+                .names("nameform2")
+                .names("nameform3")
+                .removeName("nameform2")
+                .removeRequiredAttribute("cn")
+                .removeOptionalAttribute("l")
+                .addNoOverwriteToSchema()
+            .toSchema();
+        // @formatter:on
 
-        // Checking if the attributes are the same :
-        assertThat(nf2.getDescription()).isEqualTo(nf1.getDescription());
-        assertThat(nf2.getDescription()).isEqualTo("Description of the new form");
+        assertThat(schema.getNameForms()).isNotEmpty();
+        final NameForm nf = schema.getNameForms().iterator().next();
 
-        assertThat(nf2.getExtraPropertyNames()).isEqualTo(nf1.getExtraPropertyNames());
-        assertThat(nf2.getExtraPropertyNames().iterator().next()).isEqualTo("X-ORIGIN");
+        assertThat(nf.getNames()).hasSize(2);
+        assertThat(nf.getNames()).contains("nameform1");
+        assertThat(nf.getNames()).contains("nameform3");
 
-        assertThat(nf2.getNameOrOID()).isEqualTo(nf1.getNameOrOID());
-        assertThat(nf2.getNameOrOID()).isEqualTo("MyNewForm");
+        assertThat(nf.getRequiredAttributes().size()).isEqualTo(2);
+        assertThat(nf.getRequiredAttributes().toString()).contains("'sn'");
+        assertThat(nf.getRequiredAttributes().toString()).contains("uid");
 
-        assertThat(nf2.getOID()).isEqualTo(nf1.getOID());
-        assertThat(nf2.getOID()).isEqualTo("1.3.6.1.4.1.1466.115.121.1.35");
+        assertThat(nf.getOptionalAttributes().size()).isEqualTo(1);
+    }
 
-        // Required and optional attributes are not empty :
-        assertThat(nf2.getOptionalAttributes()).isNotEmpty();
-        assertThat(nf2.getOptionalAttributes()).isEqualTo(nf1.getOptionalAttributes());
+    /**
+     * Trying to remove attributes from a duplicated name form.
+     *
+     * @throws SchemaException
+     */
+    @Test()
+    public final void testDuplicatesNameFormAndRemovesAttributes() {
 
-        assertThat(nf2.getRequiredAttributes()).isNotEmpty();
-        assertThat(nf2.getRequiredAttributes()).isEqualTo(nf1.getRequiredAttributes());
+        // @formatter:off
+        Schema schema = new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.35")
+                .description("Description of the new form")
+                .names("MyNewForm")
+                .structuralObjectClassOID("person")
+                .extraProperties("X-ORIGIN", "NameFormTestCase", "Forgerock", "extra")
+                .extraProperties("FROM", "NameFormTestCase")
+                .requiredAttributes("sn", "cn")
+                .optionalAttributes("description", "uid")
+                .addNoOverwriteToSchema()
+            .toSchema();
+        // @formatter:on
 
-        assertThat(nf2.getStructuralClass()).isEqualTo(nf1.getStructuralClass());
+        assertThat(schema.getWarnings()).isEmpty();
+        assertThat(schema.getNameForms()).isNotEmpty();
+
+        final NameForm nf = schema.getNameForms().iterator().next();
+        assertThat(nf.getOID()).isEqualTo("1.3.6.1.4.1.1466.115.121.1.35");
+        assertThat(nf.getRequiredAttributes().size()).isEqualTo(2);
+        assertThat(nf.getOptionalAttributes().size()).isEqualTo(2);
+
+        // @formatter:off.
+        SchemaBuilder sb = new SchemaBuilder(Schema.getCoreSchema());
+        Builder nfBuilder = new Builder(nf, sb)
+                    .names("Dolly")
+                    .oid("1.3.6.1.4.1.1466.115.121.1.36")
+                    .removeOptionalAttribute("uid")
+                    .removeOptionalAttribute("nonExistentUid")
+                    .requiredAttributes("street")
+                    .removeRequiredAttribute("sn")
+                    .removeExtraProperties("X-ORIGIN", "extra")
+                    .removeExtraProperties("X-ORIGIN", "Forgerock")
+                    .removeExtraProperties("FROM", null);
+        // @formatter:on
+        sb.addSchema(schema, true);
+        sb.addSchema(nfBuilder.addToSchema().toSchema(), true);
+        Schema finalSchema =  sb.toSchema();
+
+        assertThat(finalSchema.getNameForms()).isNotEmpty();
+        assertThat(finalSchema.getNameForms().size()).isEqualTo(2);
+        assertThat(finalSchema.getWarnings()).isEmpty();
+
+        final Iterator<NameForm> i = finalSchema.getNameForms().iterator();
+        i.next(); // Jump the first element (== nf)
+        final NameForm dolly = i.next();
+        assertThat(dolly.getOID()).isEqualTo("1.3.6.1.4.1.1466.115.121.1.36");
+
+        assertThat(dolly.getRequiredAttributes().size()).isEqualTo(2);
+        assertThat(dolly.getRequiredAttributes().toString()).contains("street");
+        assertThat(dolly.getRequiredAttributes().toString()).contains("cn");
+
+        assertThat(dolly.getOptionalAttributes().size()).isEqualTo(1);
+        assertThat(dolly.getExtraProperty("X-ORIGIN").size()).isEqualTo(1);
+        assertThat(dolly.getExtraProperty("FROM")).isEmpty();
+    }
+
+    /**
+     * Clears attributes from a duplicated name form.
+     *
+     * @throws SchemaException
+     */
+    @Test()
+    public final void testDuplicatesNameFormAndClears() {
+
+        final SchemaBuilder sb = new SchemaBuilder();
+        sb.addSchema(Schema.getCoreSchema(), false);
+        // @formatter:off
+        final Builder nfb = new Builder("1.3.6.1.4.1.1466.115.121.1.35", sb);
+        nfb.description("Description of the new form")
+            .names("MyNewForm")
+            .structuralObjectClassOID("person")
+            .extraProperties("X-ORIGIN", "NameFormTestCase", "Forgerock", "extra")
+            .extraProperties("FROM", "NameFormTestCase")
+            .requiredAttributes("sn", "cn")
+            .optionalAttributes("description", "uid")
+            .addNoOverwriteToSchema();
+
+        Schema schema = sb.toSchema();
+        assertThat(schema.getWarnings()).isEmpty();
+        assertThat(schema.getNameForms()).isNotEmpty();
+
+        final NameForm nf = schema.getNameForms().iterator().next();
+        assertThat(nf.getOID()).isEqualTo("1.3.6.1.4.1.1466.115.121.1.35");
+        assertThat(nf.getRequiredAttributes().size()).isEqualTo(2);
+        assertThat(nf.getOptionalAttributes().size()).isEqualTo(2);
+        assertThat(nf.getExtraPropertyNames().size()).isEqualTo(2);
+
+        sb.buildNameForm(nf)
+            .removeAllNames()
+            .names("Dolly")
+            .removeName("thisOneDoesntExist")
+            .oid("1.3.6.1.4.1.1466.115.121.1.36")
+            .removeAllOptionalAttributes()
+            .clearExtraProperties()
+            .removeAllRequiredAttributes()
+            .requiredAttributes("businessCategory")
+            .addToSchema();
+        schema = sb.toSchema();
+        assertThat(schema.getNameForms()).isNotEmpty();
+        assertThat(schema.getNameForms().size()).isEqualTo(2);
+        assertThat(schema.getWarnings()).isEmpty();
+
+        final Iterator<NameForm> i = schema.getNameForms().iterator();
+        i.next(); // Jump the first element (== nf)
+        final NameForm dolly = i.next();
+        assertThat(dolly.getOID()).isEqualTo("1.3.6.1.4.1.1466.115.121.1.36");
+
+        assertThat(dolly.getNames().size()).isEqualTo(1);
+        assertThat(dolly.getNames().get(0)).isEqualTo("Dolly");
+        assertThat(dolly.getRequiredAttributes().size()).isEqualTo(1);
+        assertThat(dolly.getRequiredAttributes().iterator().next().getOID()).isEqualTo("2.5.4.15");
+        assertThat(dolly.getRequiredAttributes().iterator().next().getNameOrOID()).isEqualTo("businessCategory");
+
+        assertThat(dolly.getOptionalAttributes().size()).isEqualTo(0);
+
+        assertThat(dolly.getExtraPropertyNames().size()).isEqualTo(0);
+    }
+
+    /**
+     * Adds several name forms to the same schema builder.
+     *
+     * @throws SchemaException
+     */
+    @Test()
+    public final void testAddsSeveralFormsToSchemaBuilder() {
+
+        // @formatter:off
+        final Schema schema = new SchemaBuilder(Schema.getCoreSchema())
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.35")
+                .description("Description of the new form")
+                .names("MyNewForm")
+                .structuralObjectClassOID("person")
+                .extraProperties("X-ORIGIN", "NameFormTestCase", "Forgerock", "extra")
+                .requiredAttributes("sn", "cn")
+                .optionalAttributes("description", "uid")
+                .addNoOverwriteToSchema()
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.36")
+                .description("Description of the second form")
+                .names("SecondForm")
+                .structuralObjectClassOID("organization")
+                .extraProperties("X-ORIGIN", "NameFormTestCase2")
+                .requiredAttributes("name")
+                .optionalAttributes("owner")
+                .addNoOverwriteToSchema()
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.37")
+                .description("Description of the third form")
+                .names("ThirdForm")
+                .structuralObjectClassOID("groupOfNames")
+                .extraProperties("X-ORIGIN", "NameFormTestCase3", "ForgeRock")
+                .requiredAttributes("sn", "l")
+                .optionalAttributes("description", "uid")
+                .description("Description of the third form")
+                .addNoOverwriteToSchema()
+                // we overwritten the third name form.
+            .buildNameForm("1.3.6.1.4.1.1466.115.121.1.37")
+                .names("ThirdFormOverwritten")
+                .structuralObjectClassOID("groupOfNames")
+                .extraProperties("X-ORIGIN", "RFC 2252")
+                .requiredAttributes("sn", "l")
+                .optionalAttributes("description", "uid")
+                .addToSchema()
+            .toSchema();
+        // @formatter:on
+
+        assertThat(schema.getWarnings()).isEmpty();
+        assertThat(schema.getNameForms().size()).isEqualTo(3);
+        assertThat(schema.getNameForm("MyNewForm").getOID()).isEqualTo(
+                "1.3.6.1.4.1.1466.115.121.1.35");
+        assertThat(schema.getNameForm("SecondForm").getOID()).isEqualTo(
+                "1.3.6.1.4.1.1466.115.121.1.36");
+        // The third form is completely overwritten.
+        assertThat(schema.getNameForm("ThirdFormOverwritten").getOID()).isEqualTo(
+                "1.3.6.1.4.1.1466.115.121.1.37");
+        assertThat(schema.getNameForm("ThirdFormOverwritten").getDescription()).isEmpty();
+        assertThat(schema.getNameForm("ThirdFormOverwritten").getExtraProperty("X-ORIGIN").get(0))
+                .isEqualTo("RFC 2252");
     }
 }

--
Gitblit v1.10.0