From 22b6772ee6ba596eb37ff79ce7f6972d86f8a058 Mon Sep 17 00:00:00 2001
From: Gaetan Boismal <gaetan.boismal@forgerock.com>
Date: Wed, 21 Jan 2015 16:50:38 +0000
Subject: [PATCH] OPENDJ-1729 (CR-5816) Add fluent builder for DIT content rules

---
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java          |  143 ++++-------
 opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/DITContentRuleTestCase.java |  171 ++++++++++++++
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/DITContentRule.java         |  393 ++++++++++++++++++++++++++++++-
 opendj-sdk/opendj-sdk                                                                             |    6 
 4 files changed, 600 insertions(+), 113 deletions(-)

diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/DITContentRule.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/DITContentRule.java
index 4ef307d..4743532 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/DITContentRule.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/DITContentRule.java
@@ -22,16 +22,23 @@
  *
  *
  *      Copyright 2009 Sun Microsystems, Inc.
- *      Portions copyright 2011 ForgeRock AS
+ *      Portions copyright 2011-2015 ForgeRock AS
  */
 
 package org.forgerock.opendj.ldap.schema;
 
+import static java.util.Arrays.*;
+
+import static org.forgerock.opendj.ldap.schema.SchemaUtils.*;
+
 import static com.forgerock.opendj.ldap.CoreMessages.*;
 
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -47,6 +54,359 @@
  */
 public final class DITContentRule extends SchemaElement {
 
+    /** A fluent API for incrementally constructing DIT content rule. */
+    public static final class Builder extends SchemaElementBuilder<Builder> {
+        private String structuralClassOID;
+        private final List<String> names = new LinkedList<String>();
+        private boolean isObsolete;
+        private final Set<String> auxiliaryClassOIDs = new LinkedHashSet<String>();
+        private final Set<String> optionalAttributeOIDs = new LinkedHashSet<String>();
+        private final Set<String> prohibitedAttributeOIDs = new LinkedHashSet<String>();
+        private final Set<String> requiredAttributeOIDs = new LinkedHashSet<String>();
+
+        Builder(final DITContentRule contentRule, final SchemaBuilder schemaBuilder) {
+            super(schemaBuilder, contentRule);
+            structuralClassOID = contentRule.structuralClassOID;
+            names.addAll(contentRule.getNames());
+            isObsolete = contentRule.isObsolete;
+            auxiliaryClassOIDs.addAll(contentRule.auxiliaryClassOIDs);
+            optionalAttributeOIDs.addAll(contentRule.optionalAttributeOIDs);
+            prohibitedAttributeOIDs.addAll(contentRule.prohibitedAttributeOIDs);
+            requiredAttributeOIDs.addAll(contentRule.requiredAttributeOIDs);
+        }
+
+        Builder(final String structuralClassOID, final SchemaBuilder builder) {
+            super(builder);
+            this.structuralClassOID = structuralClassOID;
+        }
+
+        /**
+         * Adds this DIT content rule to the schema, throwing an
+         * {@code  ConflictingSchemaElementException} if there is an existing DIT
+         * content rule with the same structural object class OID.
+         *
+         * @return The parent schema builder.
+         * @throws ConflictingSchemaElementException
+         *             If there is an existing DIT content rule with the same
+         *             structural object class OID.
+         */
+        public SchemaBuilder addToSchema() {
+            return getSchemaBuilder().addDITContentRule(new DITContentRule(this), false);
+        }
+
+        /**
+         * Adds this DIT content rule to the schema overwriting any existing
+         * content rule with the same structural class OID.
+         *
+         * @return The parent schema builder.
+         */
+        public SchemaBuilder addToSchemaOverwrite() {
+            return getSchemaBuilder().addDITContentRule(new DITContentRule(this), true);
+        }
+
+        /**
+         * Adds the provided auxiliary classes to the list of auxiliary object
+         * classes that entries subject to this DIT content rule may belong to.
+         *
+         * @param objectClassNamesOrOIDs
+         *            The list of auxiliary class names or OIDs.
+         * @return This builder.
+         */
+        public Builder auxiliaryObjectClasses(final Collection<String> objectClassNamesOrOIDs) {
+            this.auxiliaryClassOIDs.addAll(objectClassNamesOrOIDs);
+            return this;
+        }
+
+        /**
+         * Adds the provided auxiliary classes to the list of auxiliary object
+         * classes that entries subject to this DIT content rule may belong to.
+         *
+         * @param objectClassNamesOrOIDs
+         *            The list of auxiliary class names or OIDs.
+         * @return This builder.
+         */
+        public Builder auxiliaryObjectClasses(String... objectClassNamesOrOIDs) {
+            this.auxiliaryClassOIDs.addAll(asList(objectClassNamesOrOIDs));
+            return this;
+        }
+
+        @Override
+        public Builder description(final String description) {
+            return description0(description);
+        }
+
+        @Override
+        public Builder extraProperties(final Map<String, List<String>> extraProperties) {
+            return extraProperties0(extraProperties);
+        }
+
+        @Override
+        public Builder extraProperties(final String extensionName, final String... extensionValues) {
+            return extraProperties0(extensionName, extensionValues);
+        }
+
+        @Override
+        Builder getThis() {
+            return this;
+        }
+
+        /**
+         * Adds the provided user friendly names.
+         *
+         * @param names
+         *            The user friendly names.
+         * @return This builder.
+         */
+        public Builder names(final Collection<String> names) {
+            this.names.addAll(names);
+            return this;
+        }
+
+        /**
+         * Adds the provided user friendly names.
+         *
+         * @param names
+         *            The user friendly names.
+         * @return This builder.
+         */
+        public Builder names(final String... names) {
+            return names(asList(names));
+        }
+
+        /**
+         * Specifies whether this schema element is obsolete.
+         *
+         * @param isObsolete
+         *            {@code true} if this schema element is obsolete (default
+         *            is {@code false}).
+         * @return This builder.
+         */
+        public Builder obsolete(final boolean isObsolete) {
+            this.isObsolete = isObsolete;
+            return this;
+        }
+
+        /**
+         * Adds the provided optional attributes to the list of attribute types
+         * that entries subject to this DIT content rule may contain.
+         *
+         * @param attributeNamesOrOIDs
+         *            The list of optional attribute names or OIDs.
+         * @return This builder.
+         */
+        public Builder optionalAttributes(final Collection<String> attributeNamesOrOIDs) {
+            this.optionalAttributeOIDs.addAll(attributeNamesOrOIDs);
+            return this;
+        }
+
+        /**
+         * Adds the provided optional attributes to the list of attribute types
+         * that entries subject to this DIT content rule may contain.
+         *
+         * @param attributeNamesOrOIDs
+         *            The list of optional attribute names or OIDs.
+         * @return This builder.
+         */
+        public Builder optionalAttributes(final String... attributeNamesOrOIDs) {
+            this.optionalAttributeOIDs.addAll(asList(attributeNamesOrOIDs));
+            return this;
+        }
+
+        /**
+         * Adds the provided prohibited attributes to the list of attribute types
+         * that entries subject to this DIT content rule must not contain.
+         *
+         * @param attributeNamesOrOIDs
+         *            The list of prohibited attribute names or OIDs.
+         * @return This builder.
+         */
+        public Builder prohibitedAttributes(final Collection<String> attributeNamesOrOIDs) {
+            this.prohibitedAttributeOIDs.addAll(attributeNamesOrOIDs);
+            return this;
+        }
+
+        /**
+         * Adds the provided prohibited attributes to the list of attribute types
+         * that entries subject to this DIT content rule must not contain.
+         *
+         * @param attributeNamesOrOIDs
+         *            The list of prohibited attribute names or OIDs.
+         * @return This builder.
+         */
+        public Builder prohibitedAttributes(final String... attributeNamesOrOIDs) {
+            this.prohibitedAttributeOIDs.addAll(asList(attributeNamesOrOIDs));
+            return this;
+        }
+
+        /**
+         * Clears the list of auxiliary object classes that entries subject to
+         * this DIT content rule may belong to.
+         *
+         * @return This builder.
+         */
+        public Builder removeAllAuxiliaryObjectClasses() {
+            this.auxiliaryClassOIDs.clear();
+            return this;
+        }
+
+        @Override
+        public Builder removeAllExtraProperties() {
+            return removeAllExtraProperties0();
+        }
+
+        /**
+         * Removes all user defined names.
+         *
+         * @return This builder.
+         */
+        public Builder removeAllNames() {
+            this.names.clear();
+            return this;
+        }
+
+        /**
+         * Clears the list of attribute types that entries subject to this DIT
+         * content rule may contain.
+         *
+         * @return This builder.
+         */
+        public Builder removeAllOptionalAttributes() {
+            this.optionalAttributeOIDs.clear();
+            return this;
+        }
+
+        /**
+         * Clears the list of attribute types that entries subject to this DIT
+         * content rule must not contain.
+         *
+         * @return This builder.
+         */
+        public Builder removeAllProhibitedAttributes() {
+            this.prohibitedAttributeOIDs.clear();
+            return this;
+        }
+
+        /**
+         * Clears the list of attribute types that entries subject to this DIT
+         * content rule must contain.
+         *
+         * @return This builder.
+         */
+        public Builder removeAllRequiredAttributes() {
+            this.requiredAttributeOIDs.clear();
+            return this;
+        }
+
+        /**
+         * Removes the provided object class in the list of auxiliary object classes that entries subject to
+         * this DIT content rule may belong to.
+         *
+         * @param objectClassNameOrOID
+         *            The auxiliary object class name or OID to be removed.
+         * @return This builder.
+         */
+        public Builder removeAuxiliaryObjectClass(String objectClassNameOrOID) {
+            this.auxiliaryClassOIDs.remove(objectClassNameOrOID);
+            return this;
+        }
+
+        @Override
+        public Builder removeExtraProperty(String extensionName, String... extensionValues) {
+            return removeExtraProperty0(extensionName, extensionValues);
+        }
+
+        /**
+         * Removes the provided user defined name.
+         *
+         * @param name
+         *            The user defined name to be removed.
+         * @return This builder.
+         */
+        public Builder removeName(String name) {
+            this.names.remove(name);
+            return this;
+        }
+
+        /**
+         * Removes the provided optional attribute in the list of attribute
+         * types that entries subject to this DIT content rule may contain.
+         *
+         * @param attributeNameOrOID
+         *            The optional attribute name or OID to be removed.
+         * @return This builder.
+         */
+        public Builder removeOptionalAttribute(String attributeNameOrOID) {
+            this.optionalAttributeOIDs.remove(attributeNameOrOID);
+            return this;
+        }
+
+        /**
+         * Removes the provided prohibited attribute in the list of attribute
+         * types that entries subject to this DIT content rule must not contain.
+         *
+         * @param attributeNameOrOID
+         *            The prohibited attribute name or OID to be removed.
+         * @return This builder.
+         */
+        public Builder removeProhibitedAttribute(String attributeNameOrOID) {
+            this.prohibitedAttributeOIDs.remove(attributeNameOrOID);
+            return this;
+        }
+
+        /**
+         * Removes the provided required attribute in the list of attribute
+         * types that entries subject to this DIT content rule must contain.
+         *
+         * @param attributeNameOrOID
+         *            The provided required attribute name or OID to be removed.
+         * @return This builder.
+         */
+        public Builder removeRequiredAttribute(String attributeNameOrOID) {
+            this.requiredAttributeOIDs.remove(attributeNameOrOID);
+            return this;
+        }
+
+        /**
+         * Adds the provided attribute to the list of attribute types that
+         * entries subject to this DIT content rule must contain.
+         *
+         * @param attributeNamesOrOIDs
+         *            The list of required attribute names or OIDs.
+         * @return This builder.
+         */
+        public Builder requiredAttributes(final Collection<String> attributeNamesOrOIDs) {
+            this.requiredAttributeOIDs.addAll(attributeNamesOrOIDs);
+            return this;
+        }
+
+        /**
+         * Adds the provided attribute to the list of attribute types that
+         * entries subject to this DIT content rule must contain.
+         *
+         * @param attributeNamesOrOIDs
+         *            The list of required attribute names or OIDs.
+         * @return This builder.
+         */
+        public Builder requiredAttributes(final String... attributeNamesOrOIDs) {
+            this.requiredAttributeOIDs.addAll(asList(attributeNamesOrOIDs));
+            return this;
+        }
+
+        /**
+         * Sets the structural class OID which uniquely identifies this DIT
+         * content rule.
+         *
+         * @param strucuralClassOID
+         *            The numeric OID.
+         * @return This builder.
+         */
+        public Builder structuralClassOID(String strucuralClassOID) {
+            this.structuralClassOID = strucuralClassOID;
+            return this;
+        }
+
+    }
+
     /** The structural objectclass for this DIT content rule. */
     private final String structuralClassOID;
 
@@ -78,22 +438,17 @@
     private Set<AttributeType> prohibitedAttributes = Collections.emptySet();
     private Set<AttributeType> requiredAttributes = Collections.emptySet();
 
-    DITContentRule(final String structuralClassOID, final List<String> names,
-            final String description, final boolean obsolete, final Set<String> auxiliaryClassOIDs,
-            final Set<String> optionalAttributeOIDs, final Set<String> prohibitedAttributeOIDs,
-            final Set<String> requiredAttributeOIDs,
-            final Map<String, List<String>> extraProperties, final String definition) {
-        super(description, extraProperties, definition);
+    private DITContentRule(final Builder builder) {
+        super(builder);
+        Reject.ifNull(builder.structuralClassOID);
 
-        Reject.ifNull(structuralClassOID, names, auxiliaryClassOIDs, optionalAttributeOIDs, prohibitedAttributeOIDs,
-                requiredAttributeOIDs);
-        this.names = names;
-        this.isObsolete = obsolete;
-        this.structuralClassOID = structuralClassOID;
-        this.auxiliaryClassOIDs = auxiliaryClassOIDs;
-        this.optionalAttributeOIDs = optionalAttributeOIDs;
-        this.prohibitedAttributeOIDs = prohibitedAttributeOIDs;
-        this.requiredAttributeOIDs = requiredAttributeOIDs;
+        structuralClassOID = builder.structuralClassOID;
+        names = unmodifiableCopyOfList(builder.names);
+        isObsolete = builder.isObsolete;
+        auxiliaryClassOIDs = unmodifiableCopyOfSet(builder.auxiliaryClassOIDs);
+        optionalAttributeOIDs = unmodifiableCopyOfSet(builder.optionalAttributeOIDs);
+        prohibitedAttributeOIDs = unmodifiableCopyOfSet(builder.prohibitedAttributeOIDs);
+        requiredAttributeOIDs = unmodifiableCopyOfSet(builder.requiredAttributeOIDs);
     }
 
     /**
@@ -296,12 +651,6 @@
         return isRequired(attributeType) || isOptional(attributeType);
     }
 
-    DITContentRule duplicate() {
-        return new DITContentRule(structuralClassOID, names, getDescription(), isObsolete,
-                auxiliaryClassOIDs, optionalAttributeOIDs, prohibitedAttributeOIDs,
-                requiredAttributeOIDs, getExtraProperties(), toString());
-    }
-
     @Override
     void toStringContent(final StringBuilder buffer) {
         buffer.append(structuralClassOID);
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java
index fabd3f7..c746d00 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java
@@ -71,6 +71,7 @@
 import org.forgerock.opendj.ldap.requests.Requests;
 import org.forgerock.opendj.ldap.requests.SearchRequest;
 import org.forgerock.opendj.ldap.responses.SearchResultEntry;
+import org.forgerock.opendj.ldap.schema.DITContentRule.Builder;
 import org.forgerock.util.Reject;
 import org.forgerock.util.promise.AsyncFunction;
 import org.forgerock.util.promise.Function;
@@ -446,16 +447,9 @@
             reader.skipWhitespaces();
 
             // The next set of characters must be the OID.
-            final String structuralClass = readOID(reader, allowsMalformedNamesAndOptions());
-
-            List<String> names = Collections.emptyList();
-            String description = "".intern();
-            boolean isObsolete = false;
-            Set<String> auxiliaryClasses = Collections.emptySet();
-            Set<String> optionalAttributes = Collections.emptySet();
-            Set<String> prohibitedAttributes = Collections.emptySet();
-            Set<String> requiredAttributes = Collections.emptySet();
-            Map<String, List<String>> extraProperties = Collections.emptyMap();
+            final DITContentRule.Builder contentRuleBuilder =
+                    buildDITContentRule(readOID(reader, allowsMalformedNamesAndOptions()));
+            contentRuleBuilder.definition(definition);
 
             // At this point, we should have a pretty specific syntax that
             // describes what may come next, but some of the components are
@@ -472,110 +466,42 @@
                     // No more tokens.
                     break;
                 } else if ("name".equalsIgnoreCase(tokenName)) {
-                    names = readNameDescriptors(reader, allowsMalformedNamesAndOptions());
+                    contentRuleBuilder.names(readNameDescriptors(reader, allowsMalformedNamesAndOptions()));
                 } else if ("desc".equalsIgnoreCase(tokenName)) {
                     // This specifies the description for the attribute type. It
                     // is an arbitrary string of characters enclosed in single
                     // quotes.
-                    description = readQuotedString(reader);
+                    contentRuleBuilder.description(readQuotedString(reader));
                 } else if ("obsolete".equalsIgnoreCase(tokenName)) {
                     // This indicates whether the attribute type should be
-                    // considered obsolete. We do not need to do any more
-                    // parsing for this token.
-                    isObsolete = true;
+                    // considered obsolete.
+                    contentRuleBuilder.obsolete(true);
                 } else if ("aux".equalsIgnoreCase(tokenName)) {
-                    auxiliaryClasses = readOIDs(reader, allowsMalformedNamesAndOptions());
+                    contentRuleBuilder.auxiliaryObjectClasses(readOIDs(reader, allowsMalformedNamesAndOptions()));
                 } else if ("must".equalsIgnoreCase(tokenName)) {
-                    requiredAttributes = readOIDs(reader, allowsMalformedNamesAndOptions());
+                    contentRuleBuilder.requiredAttributes(readOIDs(reader, allowsMalformedNamesAndOptions()));
                 } else if ("may".equalsIgnoreCase(tokenName)) {
-                    optionalAttributes = readOIDs(reader, allowsMalformedNamesAndOptions());
+                    contentRuleBuilder.optionalAttributes(readOIDs(reader, allowsMalformedNamesAndOptions()));
                 } else if ("not".equalsIgnoreCase(tokenName)) {
-                    prohibitedAttributes = readOIDs(reader, allowsMalformedNamesAndOptions());
+                    contentRuleBuilder.prohibitedAttributes(readOIDs(reader, allowsMalformedNamesAndOptions()));
                 } 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, readExtensions(reader));
+                    contentRuleBuilder.extraProperties(tokenName, readExtensions(reader));
                 } else {
                     throw new LocalizedIllegalArgumentException(
                         ERR_ATTR_SYNTAX_DCR_ILLEGAL_TOKEN1.get(definition, tokenName));
                 }
             }
 
-            if (!extraProperties.isEmpty()) {
-                extraProperties = Collections.unmodifiableMap(extraProperties);
-            }
-
-            final DITContentRule rule =
-                    new DITContentRule(structuralClass, names, description, isObsolete,
-                            auxiliaryClasses, optionalAttributes, prohibitedAttributes,
-                            requiredAttributes, extraProperties, definition);
-            addDITContentRule(rule, overwrite);
+            return overwrite ? contentRuleBuilder.addToSchemaOverwrite() : contentRuleBuilder.addToSchema();
         } catch (final DecodeException e) {
-            final LocalizableMessage msg =
-                    ERR_ATTR_SYNTAX_DCR_INVALID1.get(definition, e.getMessageObject());
+            final LocalizableMessage msg = ERR_ATTR_SYNTAX_DCR_INVALID1.get(definition, e.getMessageObject());
             throw new LocalizedIllegalArgumentException(msg, e.getCause());
         }
-        return this;
-    }
-
-    /**
-     * Adds the provided DIT content rule definition to this schema builder.
-     *
-     * @param structuralClass
-     *            The name of the structural object class to which the DIT
-     *            content rule applies.
-     * @param names
-     *            The user-friendly names of the DIT content rule definition.
-     * @param description
-     *            The description of the DIT content rule definition.
-     * @param obsolete
-     *            {@code true} if the DIT content rule definition is obsolete,
-     *            otherwise {@code false}.
-     * @param auxiliaryClasses
-     *            A list of auxiliary object classes that entries subject to the
-     *            DIT content rule may belong to.
-     * @param optionalAttributes
-     *            A list of attribute types that entries subject to the DIT
-     *            content rule may contain.
-     * @param prohibitedAttributes
-     *            A list of attribute types that entries subject to the DIT
-     *            content rule must not contain.
-     * @param requiredAttributes
-     *            A list of attribute types that entries subject to the DIT
-     *            content rule must contain.
-     * @param extraProperties
-     *            A map containing additional properties associated with the DIT
-     *            content rule definition.
-     * @param overwrite
-     *            {@code true} if any existing DIT content rule 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.
-     */
-    SchemaBuilder addDITContentRule(final String structuralClass, final List<String> names,
-            final String description, final boolean obsolete, final Set<String> auxiliaryClasses,
-            final Set<String> optionalAttributes, final Set<String> prohibitedAttributes,
-            final Set<String> requiredAttributes, final Map<String, List<String>> extraProperties,
-            final boolean overwrite) {
-        lazyInitBuilder();
-
-        final DITContentRule rule =
-                new DITContentRule(structuralClass, unmodifiableCopyOfList(names), description,
-                        obsolete, unmodifiableCopyOfSet(auxiliaryClasses),
-                        unmodifiableCopyOfSet(optionalAttributes),
-                        unmodifiableCopyOfSet(prohibitedAttributes),
-                        unmodifiableCopyOfSet(requiredAttributes),
-                        unmodifiableCopyOfExtraProperties(extraProperties), null);
-        addDITContentRule(rule, overwrite);
-        return this;
     }
 
     /**
@@ -1230,6 +1156,24 @@
 
     /**
      * Returns a builder which can be used for incrementally constructing a new
+     * DIT content rule before adding it to the schema. Example usage:
+     *
+     * <pre>
+     * SchemaBuilder builder = ...;
+     * builder.buildDITContentRule("structuralobjectclass-oid").name("DIT content rule name").addToSchema();
+     * </pre>
+     *
+     * @param structuralClassOID
+     *            The OID of the structural objectclass for the DIT content rule to build.
+     * @return A builder to continue building the DITContentRule.
+     */
+    public Builder buildDITContentRule(String structuralClassOID) {
+        lazyInitBuilder();
+        return new DITContentRule.Builder(structuralClassOID, this);
+    }
+
+    /**
+     * Returns a builder which can be used for incrementally constructing a new
      * name form before adding it to the schema. Example usage:
      *
      * <pre>
@@ -1280,6 +1224,21 @@
     }
 
     /**
+     * Returns a DIT content rule builder whose fields are initialized to the
+     * values of the provided DIT content rule. This method should be used when
+     * duplicating DIT content rules from external schemas or when modifying
+     * existing DIT content rules.
+     *
+     * @param contentRule
+     *            The DIT content rule source.
+     * @return A builder to continue building the DITContentRule.
+     */
+    public DITContentRule.Builder buildDITContentRule(DITContentRule contentRule) {
+        lazyInitBuilder();
+        return new DITContentRule.Builder(contentRule, this);
+    }
+
+    /**
      * Duplicates the matching rule.
      *
      * @param matchingRule
@@ -2273,7 +2232,7 @@
         return this;
     }
 
-    private void addDITContentRule(final DITContentRule rule, final boolean overwrite) {
+    SchemaBuilder addDITContentRule(final DITContentRule rule, final boolean overwrite) {
         DITContentRule conflictingRule;
         if (numericOID2ContentRules.containsKey(rule.getStructuralClassOID())) {
             conflictingRule = numericOID2ContentRules.get(rule.getStructuralClassOID());
@@ -2300,6 +2259,8 @@
                 rules.add(rule);
             }
         }
+
+        return this;
     }
 
     private void addDITStructureRule(final DITStructureRule rule, final boolean overwrite) {
@@ -2491,7 +2452,7 @@
         }
 
         for (final DITContentRule contentRule : schema.getDITContentRules()) {
-            addDITContentRule(contentRule.duplicate(), overwrite);
+            addDITContentRule(contentRule, overwrite);
         }
 
         for (final DITStructureRule structureRule : schema.getDITStuctureRules()) {
diff --git a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/DITContentRuleTestCase.java b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/DITContentRuleTestCase.java
new file mode 100644
index 0000000..b3246f4
--- /dev/null
+++ b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/DITContentRuleTestCase.java
@@ -0,0 +1,171 @@
+/*
+ * 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 2015 ForgeRock AS
+ */
+package org.forgerock.opendj.ldap.schema;
+
+import static java.util.Collections.*;
+
+import static org.fest.assertions.Assertions.*;
+import static org.forgerock.opendj.ldap.schema.Schema.*;
+
+import java.util.Collections;
+
+import org.testng.annotations.Test;
+
+public class DITContentRuleTestCase extends AbstractSchemaTestCase {
+
+    /** Adds a DIT content rule on "device" structural object class. */
+    @Test
+    public void testValidDITContentRule() throws Exception {
+        final Schema schema = new SchemaBuilder(getCoreSchema())
+                .buildDITContentRule("2.5.6.14")
+                .names(singletonList("devicecontentrule"))
+                .description("Content rule desc")
+                .auxiliaryObjectClasses(Collections.<String> emptyList())
+                .optionalAttributes(Collections.<String> emptyList())
+                .prohibitedAttributes(singletonList("serialNumber"))
+                .requiredAttributes(singletonList("owner"))
+                .addToSchema()
+                .toSchema();
+
+        assertThat(schema.getWarnings()).isEmpty();
+        final DITContentRule cr = schema.getDITContentRule("devicecontentrule");
+        assertThat(cr).isNotNull();
+        assertThat(cr.getStructuralClassOID()).isEqualTo("2.5.6.14");
+        assertThat(cr.getNames()).containsOnly("devicecontentrule");
+        assertThat(cr.getAuxiliaryClasses()).hasSize(0);
+        assertThat(cr.getOptionalAttributes()).hasSize(0);
+        assertThat(cr.getProhibitedAttributes()).hasSize(1);
+        assertThat(cr.getRequiredAttributes()).hasSize(1);
+        assertThat(cr.getDescription()).isEqualTo("Content rule desc");
+        assertThat(cr.isObsolete()).isFalse();
+    }
+
+    /** Adds a DIT content rule on "organization" object class and then uses it to create another one. **/
+    @Test
+    public void testCopyConstructor() throws Exception {
+        final Schema schema = new SchemaBuilder(getCoreSchema())
+                .buildDITContentRule("2.5.6.4")
+                .names(singletonList("organizationcontentrule"))
+                .description("Content rule desc")
+                .auxiliaryObjectClasses(singletonList("2.5.6.22"))
+                .optionalAttributes(singletonList("searchGuide"))
+                .prohibitedAttributes(singletonList("postOfficeBox"))
+                .requiredAttributes(singletonList("telephoneNumber"))
+                .addToSchema()
+                .toSchema();
+        assertThat(schema.getWarnings()).isEmpty();
+
+        final Schema schemaCopy = new SchemaBuilder(getCoreSchema())
+                .buildDITContentRule(schema.getDITContentRule("organizationcontentrule"))
+                .names("organizationcontentrule-copy")
+                .addToSchema()
+                .toSchema();
+        assertThat(schemaCopy.getWarnings()).isEmpty();
+
+        final DITContentRule crCopy = schemaCopy.getDITContentRule("organizationcontentrule-copy");
+        assertThat(crCopy).isNotNull();
+        assertThat(crCopy.getStructuralClassOID()).isEqualTo("2.5.6.4");
+        assertThat(crCopy.getNames()).containsOnly("organizationcontentrule", "organizationcontentrule-copy");
+        assertThat(crCopy.getAuxiliaryClasses()).hasSize(1);
+        assertThat(crCopy.getOptionalAttributes()).hasSize(1);
+        assertThat(crCopy.getProhibitedAttributes()).hasSize(1);
+        assertThat(crCopy.getRequiredAttributes()).hasSize(1);
+        assertThat(crCopy.getDescription()).isEqualTo("Content rule desc");
+        assertThat(crCopy.isObsolete()).isFalse();
+    }
+
+    @Test(expectedExceptions = ConflictingSchemaElementException.class)
+    public void testBuilderDoesNotAllowOverwrite() throws Exception {
+        final SchemaBuilder schemaBuilder = new SchemaBuilder(getCoreSchema())
+                                           .buildDITContentRule("2.5.6.9")
+                                           .addToSchema();
+        schemaBuilder.buildDITContentRule("2.5.6.9")
+                    .addToSchema();
+    }
+
+    @Test(expectedExceptions = NullPointerException.class)
+    public void testBuilderDoesNotAllowNullStructuralOCOID() throws Exception {
+        new SchemaBuilder(getCoreSchema())
+                .buildDITContentRule((String) null)
+                .addToSchema();
+    }
+
+    @Test
+    public void testBuilderRemoveAll() throws Exception {
+        DITContentRule.Builder crBuilder = new SchemaBuilder(getCoreSchema())
+                .buildDITContentRule("2.5.6.1")
+                .names(singletonList("shouldBeRemoved"))
+                .description("My content rule")
+                .auxiliaryObjectClasses(singletonList("shouldBeRemoved"))
+                .optionalAttributes(singletonList("shouldBeRemoved"))
+                .prohibitedAttributes(singletonList("shouldBeRemoved"))
+                .requiredAttributes(singletonList("shouldBeRemoved"));
+
+        Schema schema = crBuilder.removeAllNames()
+                .removeAllAuxiliaryObjectClasses()
+                .removeAllOptionalAttributes()
+                .removeAllProhibitedAttributes()
+                .removeAllRequiredAttributes()
+                .addToSchema()
+                .toSchema();
+        assertThat(schema.getWarnings()).isEmpty();
+
+        DITContentRule cr = schema.getDITContentRule(schema.getObjectClass("2.5.6.1"));
+        assertThat(cr.getNames()).isEmpty();
+        assertThat(cr.getAuxiliaryClasses()).isEmpty();
+        assertThat(cr.getOptionalAttributes()).isEmpty();
+        assertThat(cr.getProhibitedAttributes()).isEmpty();
+        assertThat(cr.getRequiredAttributes()).isEmpty();
+    }
+
+    @Test
+    public void testBuilderRemove() throws Exception {
+        DITContentRule.Builder crBuilder = new SchemaBuilder(getCoreSchema())
+                .buildDITContentRule("2.5.6.1")
+                .names(singletonList("shouldBeRemoved"))
+                .description("My content rule")
+                .auxiliaryObjectClasses(singletonList("shouldBeRemoved"))
+                .optionalAttributes(singletonList("shouldBeRemoved"))
+                .prohibitedAttributes(singletonList("shouldBeRemoved"))
+                .requiredAttributes(singletonList("shouldBeRemoved"));
+
+        Schema schema = crBuilder.removeName("shouldBeRemoved")
+                .removeAuxiliaryObjectClass("shouldBeRemoved")
+                .removeOptionalAttribute("shouldBeRemoved")
+                .removeProhibitedAttribute("shouldBeRemoved")
+                .removeRequiredAttribute("shouldBeRemoved")
+                .addToSchema()
+                .toSchema();
+        assertThat(schema.getWarnings()).isEmpty();
+
+        DITContentRule cr = schema.getDITContentRule(schema.getObjectClass("2.5.6.1"));
+        assertThat(cr.getNames()).isEmpty();
+        assertThat(cr.getAuxiliaryClasses()).isEmpty();
+        assertThat(cr.getOptionalAttributes()).isEmpty();
+        assertThat(cr.getProhibitedAttributes()).isEmpty();
+        assertThat(cr.getRequiredAttributes()).isEmpty();
+    }
+}
diff --git a/opendj-sdk/opendj-sdk b/opendj-sdk/opendj-sdk
new file mode 100644
index 0000000..d0551cc
--- /dev/null
+++ b/opendj-sdk/opendj-sdk
@@ -0,0 +1,6 @@
+tree 14c823ec872e3b2104519bb5c84a465441b32b10
+parent e784df4bdd71d6c4e05220881ed8b6ee63d54763
+author gaetan <gaetan> 1421249488 +0100
+committer gaetan <gaetan> 1421329782 +0100
+
+Code cleanup: reformat matching rules and syntax fluent builder calls

--
Gitblit v1.10.0