From 9d44e6062ba56524e5d7738808044f00349d6738 Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Fri, 09 Sep 2016 09:45:23 +0000
Subject: [PATCH] OPENDJ-3089 OPENDJ-1237 Move code from Schema and SchemaBackend to SchemaUtils and new class SchemaWriter

---
 opendj-server-legacy/src/main/java/org/opends/server/core/SchemaHandler.java         |  239 ---------
 opendj-server-legacy/src/main/java/org/opends/server/util/SchemaUtils.java           |  250 +++++++++
 opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java     |  158 +----
 opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeTasks.java |    5 
 opendj-server-legacy/src/main/java/org/opends/server/types/SchemaWriter.java         |  486 ++++++++++++++++++
 opendj-server-legacy/src/main/java/org/opends/server/tasks/AddSchemaFileTask.java    |    6 
 opendj-server-legacy/src/main/java/org/opends/server/types/Schema.java               |  374 --------------
 7 files changed, 772 insertions(+), 746 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java
index 67039d5..3507117 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java
@@ -42,7 +42,6 @@
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
@@ -104,13 +103,13 @@
 import org.opends.server.types.Modification;
 import org.opends.server.types.Privilege;
 import org.opends.server.types.RestoreConfig;
-import org.opends.server.types.Schema;
+import org.opends.server.types.SchemaWriter;
 import org.opends.server.types.SearchFilter;
 import org.opends.server.util.BackupManager;
-import org.opends.server.util.BuildVersion;
 import org.opends.server.util.LDIFException;
 import org.opends.server.util.LDIFReader;
 import org.opends.server.util.LDIFWriter;
+import org.opends.server.util.SchemaUtils;
 import org.opends.server.util.StaticUtils;
 
 /**
@@ -241,109 +240,12 @@
       }
     }
 
-    updateConcatenatedSchema();
+    SchemaWriter.updateConcatenatedSchema();
 
     // Register with the Directory Server as a configurable component.
     currentConfig.addSchemaChangeListener(this);
   }
 
-  /**
-   * Updates the concatenated schema if changes are detected in the current schema files.
-   * <p>
-   * Identify any differences that may exist between the concatenated schema file from the last
-   * online modification and the current schema files. If there are any differences, then they
-   * should be from making changes to the schema files with the server offline.
-   */
-  private void updateConcatenatedSchema() throws InitializationException
-  {
-    try
-    {
-      // First, generate lists of elements from the current schema.
-      Set<String> newATs  = new LinkedHashSet<>();
-      Set<String> newOCs  = new LinkedHashSet<>();
-      Set<String> newNFs  = new LinkedHashSet<>();
-      Set<String> newDCRs = new LinkedHashSet<>();
-      Set<String> newDSRs = new LinkedHashSet<>();
-      Set<String> newMRUs = new LinkedHashSet<>();
-      Set<String> newLSs = new LinkedHashSet<>();
-      Schema.genConcatenatedSchema(newATs, newOCs, newNFs, newDCRs, newDSRs, newMRUs, newLSs);
-
-      // Next, generate lists of elements from the previous concatenated schema.
-      // If there isn't a previous concatenated schema, then use the base
-      // schema for the current revision.
-      File concatFile = getConcatFile();
-
-      Set<String> oldATs  = new LinkedHashSet<>();
-      Set<String> oldOCs  = new LinkedHashSet<>();
-      Set<String> oldNFs  = new LinkedHashSet<>();
-      Set<String> oldDCRs = new LinkedHashSet<>();
-      Set<String> oldDSRs = new LinkedHashSet<>();
-      Set<String> oldMRUs = new LinkedHashSet<>();
-      Set<String> oldLSs = new LinkedHashSet<>();
-      Schema.readConcatenatedSchema(concatFile, oldATs, oldOCs, oldNFs,
-                                    oldDCRs, oldDSRs, oldMRUs,oldLSs);
-
-      // Create a list of modifications and add any differences between the old
-      // and new schema into them.
-      List<Modification> mods = new LinkedList<>();
-      Schema.compareConcatenatedSchema(oldATs, newATs, attributeTypesType, mods);
-      Schema.compareConcatenatedSchema(oldOCs, newOCs, objectClassesType, mods);
-      Schema.compareConcatenatedSchema(oldNFs, newNFs, nameFormsType, mods);
-      Schema.compareConcatenatedSchema(oldDCRs, newDCRs, ditContentRulesType, mods);
-      Schema.compareConcatenatedSchema(oldDSRs, newDSRs, ditStructureRulesType, mods);
-      Schema.compareConcatenatedSchema(oldMRUs, newMRUs, matchingRuleUsesType, mods);
-      Schema.compareConcatenatedSchema(oldLSs, newLSs, ldapSyntaxesType, mods);
-      if (! mods.isEmpty())
-      {
-        // TODO : Raise an alert notification.
-
-        DirectoryServer.setOfflineSchemaChanges(mods);
-
-        // Write a new concatenated schema file with the most recent information
-        // so we don't re-find these same changes on the next startup.
-        Schema.writeConcatenatedSchema();
-      }
-    }
-    catch (InitializationException ie)
-    {
-      throw ie;
-    }
-    catch (Exception e)
-    {
-      logger.traceException(e);
-
-      logger.error(ERR_SCHEMA_ERROR_DETERMINING_SCHEMA_CHANGES, getExceptionMessage(e));
-    }
-  }
-
-  private File getConcatFile() throws InitializationException
-  {
-    File configDirectory  = new File(DirectoryServer.getConfigFile()).getParentFile();
-    File upgradeDirectory = new File(configDirectory, "upgrade");
-    File concatFile       = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME);
-    if (concatFile.exists())
-    {
-      return concatFile.getAbsoluteFile();
-    }
-
-    String fileName = SCHEMA_BASE_FILE_NAME_WITHOUT_REVISION + BuildVersion.instanceVersion().getRevision();
-    concatFile = new File(upgradeDirectory, fileName);
-    if (concatFile.exists())
-    {
-      return concatFile.getAbsoluteFile();
-    }
-
-    String runningUnitTestsStr = System.getProperty(PROPERTY_RUNNING_UNIT_TESTS);
-    if ("true".equalsIgnoreCase(runningUnitTestsStr))
-    {
-      Schema.writeConcatenatedSchema();
-      concatFile = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME);
-      return concatFile.getAbsoluteFile();
-    }
-    throw new InitializationException(ERR_SCHEMA_CANNOT_FIND_CONCAT_FILE.get(
-        upgradeDirectory.getAbsolutePath(), SCHEMA_CONCAT_FILE_NAME, concatFile.getName()));
-  }
-
   @Override
   public void closeBackend()
   {
@@ -949,7 +851,7 @@
     // Create a single file with all of the concatenated schema information
     // that we can use on startup to detect whether the schema files have been
     // edited with the server offline.
-    Schema.writeConcatenatedSchema();
+    SchemaWriter.writeConcatenatedSchema();
   }
 
   /**
@@ -973,7 +875,7 @@
           throws DirectoryException
   {
     org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
-    String oid = SchemaHandler.parseAttributeTypeOID(definition);
+    String oid = SchemaUtils.parseAttributeTypeOID(definition);
     final String finalDefinition;
     if (!currentSchema.hasAttributeType(oid))
     {
@@ -994,10 +896,10 @@
   private String completeDefinitionWhenAddingSchemaElement(String definition, Set<String> modifiedSchemaFiles)
       throws DirectoryException
   {
-    String givenSchemaFile = SchemaHandler.parseSchemaFileFromElementDefinition(definition);
+    String givenSchemaFile = SchemaUtils.parseSchemaFileFromElementDefinition(definition);
     String finalSchemaFile = givenSchemaFile == null ? FILE_USER_SCHEMA_ELEMENTS : givenSchemaFile;
     modifiedSchemaFiles.add(finalSchemaFile);
-    return SchemaHandler.addSchemaFileToElementDefinitionIfAbsent(definition, finalSchemaFile);
+    return SchemaUtils.addSchemaFileToElementDefinitionIfAbsent(definition, finalSchemaFile);
   }
 
   /**
@@ -1009,7 +911,7 @@
   private String completeDefinitionWhenReplacingSchemaElement(String definition, SchemaElement existingElement,
       Set<String> modifiedSchemaFiles) throws DirectoryException
   {
-    String givenSchemaFile = SchemaHandler.parseSchemaFileFromElementDefinition(definition);
+    String givenSchemaFile = SchemaUtils.parseSchemaFileFromElementDefinition(definition);
     String oldSchemaFile = getElementSchemaFile(existingElement);
 
     if (givenSchemaFile == null)
@@ -1019,7 +921,7 @@
         oldSchemaFile = FILE_USER_SCHEMA_ELEMENTS;
       }
       modifiedSchemaFiles.add(oldSchemaFile);
-      return SchemaHandler.addSchemaFileToElementDefinitionIfAbsent(definition, oldSchemaFile);
+      return SchemaUtils.addSchemaFileToElementDefinitionIfAbsent(definition, oldSchemaFile);
     }
     else if (oldSchemaFile == null || oldSchemaFile.equals(givenSchemaFile))
     {
@@ -1064,7 +966,7 @@
       int currentPosition, Set<String> modifiedSchemaFiles) throws DirectoryException
   {
     org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
-    String atOID = SchemaHandler.parseAttributeTypeOID(definition);
+    String atOID = SchemaUtils.parseAttributeTypeOID(definition);
 
     if (!currentSchema.hasAttributeType(atOID))
     {
@@ -1090,7 +992,7 @@
       {
         try
         {
-          String oid = SchemaHandler.parseAttributeTypeOID(v.toString());
+          String oid = SchemaUtils.parseAttributeTypeOID(v.toString());
           if (atOID.equals(oid))
           {
             // We found a match where the attribute type is added back later,
@@ -1131,7 +1033,7 @@
           throws DirectoryException
   {
     org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
-    String oid = SchemaHandler.parseObjectClassOID(definition);
+    String oid = SchemaUtils.parseObjectClassOID(definition);
     final String finalDefinition;
     if (!currentSchema.hasObjectClass(oid))
     {
@@ -1178,7 +1080,7 @@
           throws DirectoryException
   {
     org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
-    String ocOID = SchemaHandler.parseObjectClassOID(definition);
+    String ocOID = SchemaUtils.parseObjectClassOID(definition);
 
     if (!currentSchema.hasObjectClass(ocOID))
     {
@@ -1205,7 +1107,7 @@
         String oid;
         try
         {
-          oid = SchemaHandler.parseObjectClassOID(v.toString());
+          oid = SchemaUtils.parseObjectClassOID(v.toString());
         }
         catch (DirectoryException de)
         {
@@ -1247,7 +1149,7 @@
           throws DirectoryException
   {
     org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
-    String oid = SchemaHandler.parseNameFormOID(definition);
+    String oid = SchemaUtils.parseNameFormOID(definition);
     final String finalDefinition;
     if (!currentSchema.hasNameForm(oid))
     {
@@ -1293,7 +1195,7 @@
           throws DirectoryException
   {
     org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
-    String nfOID = SchemaHandler.parseNameFormOID(definition);
+    String nfOID = SchemaUtils.parseNameFormOID(definition);
 
     if (!currentSchema.hasNameForm(nfOID))
     {
@@ -1319,7 +1221,7 @@
       {
         try
         {
-          String oid = SchemaHandler.parseNameFormOID(v.toString());
+          String oid = SchemaUtils.parseNameFormOID(v.toString());
           if (nfOID.equals(oid))
           {
             // We found a match where the name form is added back later, so we
@@ -1361,7 +1263,7 @@
       Set<String> modifiedSchemaFiles) throws DirectoryException
   {
     org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
-    String oid = SchemaHandler.parseDITContentRuleOID(definition);
+    String oid = SchemaUtils.parseDITContentRuleOID(definition);
     final String finalDefinition;
     if (!currentSchema.hasDITContentRule(oid))
     {
@@ -1401,7 +1303,7 @@
       SchemaBuilder newSchemaBuilder, Set<String> modifiedSchemaFiles) throws DirectoryException
   {
     org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
-    String ruleOid = SchemaHandler.parseDITContentRuleOID(definition);
+    String ruleOid = SchemaUtils.parseDITContentRuleOID(definition);
 
     if (! currentSchema.hasDITContentRule(ruleOid))
     {
@@ -1439,7 +1341,7 @@
       throws DirectoryException
   {
     org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
-    int ruleId = SchemaHandler.parseRuleID(definition);
+    int ruleId = SchemaUtils.parseRuleID(definition);
     final String finalDefinition;
     if (!currentSchema.hasDITStructureRule(ruleId))
     {
@@ -1487,7 +1389,7 @@
           throws DirectoryException
   {
     org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
-    int ruleID = SchemaHandler.parseRuleID(definition);
+    int ruleID = SchemaUtils.parseRuleID(definition);
 
     if (!currentSchema.hasDITStructureRule(ruleID))
     {
@@ -1511,7 +1413,7 @@
 
       for (ByteString v : a)
       {
-        int id = SchemaHandler.parseRuleID(v.toString());
+        int id = SchemaUtils.parseRuleID(v.toString());
         if (ruleID == id)
         {
           // We found a match where the DIT structure rule is added back later,
@@ -1547,7 +1449,7 @@
       throws DirectoryException
   {
     org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
-    String oid = SchemaHandler.parseMatchingRuleUseOID(definition);
+    String oid = SchemaUtils.parseMatchingRuleUseOID(definition);
     final String finalDefinition;
     if (!currentSchema.hasMatchingRuleUse(oid))
     {
@@ -1589,7 +1491,7 @@
           throws DirectoryException
   {
     org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
-    String mruOid = SchemaHandler.parseMatchingRuleUseOID(definition);
+    String mruOid = SchemaUtils.parseMatchingRuleUseOID(definition);
 
     if (!currentSchema.hasMatchingRuleUse(mruOid))
     {
@@ -1628,7 +1530,7 @@
     // reject a change if a syntax with oid already exists, but I don't understand why.
     // I kept an implementation that behave like other schema elements.
     org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
-    String oid = SchemaHandler.parseSyntaxOID(definition);
+    String oid = SchemaUtils.parseSyntaxOID(definition);
     final String finalDefinition;
     if (!currentSchema.hasSyntax(oid))
     {
@@ -1652,7 +1554,7 @@
      * hence never deleted.
      */
     org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
-    String oid = SchemaHandler.parseSyntaxOID(definition);
+    String oid = SchemaUtils.parseSyntaxOID(definition);
 
     if (!currentSchema.hasSyntax(oid))
     {
@@ -2440,13 +2342,13 @@
       for (ByteString v : a)
       {
         String definition = v.toString();
-        String schemaFile = SchemaHandler.parseSchemaFileFromElementDefinition(definition);
+        String schemaFile = SchemaUtils.parseSchemaFileFromElementDefinition(definition);
         if (is02ConfigLdif(schemaFile))
         {
           continue;
         }
 
-        String oid = SchemaHandler.parseAttributeTypeOID(definition);
+        String oid = SchemaUtils.parseAttributeTypeOID(definition);
         oidList.add(oid);
         try
         {
@@ -2492,12 +2394,12 @@
         // It IS important here to allow the unknown elements that could
         // appear in the new config schema.
         String definition = v.toString();
-        String schemaFile = SchemaHandler.parseSchemaFileFromElementDefinition(definition);
+        String schemaFile = SchemaUtils.parseSchemaFileFromElementDefinition(definition);
         if (is02ConfigLdif(schemaFile))
         {
           continue;
         }
-        String oid = SchemaHandler.parseObjectClassOID(definition);
+        String oid = SchemaUtils.parseObjectClassOID(definition);
         oidList.add(oid);
         try
         {
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/core/SchemaHandler.java b/opendj-server-legacy/src/main/java/org/opends/server/core/SchemaHandler.java
index e0d13f3..61eec45 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/core/SchemaHandler.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/core/SchemaHandler.java
@@ -15,14 +15,6 @@
  */
 package org.opends.server.core;
 
-import static org.opends.messages.SchemaMessages.ERR_PARSING_LDAP_SYNTAX_OID;
-
-import static org.opends.messages.SchemaMessages.ERR_PARSING_MATCHING_RULE_USE_OID;
-import static org.opends.messages.SchemaMessages.ERR_PARSING_DIT_STRUCTURE_RULE_RULEID;
-import static org.opends.messages.SchemaMessages.ERR_PARSING_DIT_CONTENT_RULE_OID;
-import static org.opends.messages.SchemaMessages.ERR_PARSING_NAME_FORM_OID;
-import static org.opends.messages.SchemaMessages.ERR_PARSING_OBJECTCLASS_OID;
-import static org.opends.messages.SchemaMessages.ERR_PARSING_ATTRIBUTE_TYPE_OID;
 import static com.forgerock.opendj.ldap.CoreMessages.ERR_ATTR_SYNTAX_TRUNCATED_VALUE1;
 import static org.opends.messages.ConfigMessages.WARN_CONFIG_SCHEMA_CANNOT_OPEN_FILE;
 import static org.opends.server.util.ServerConstants.SCHEMA_PROPERTY_FILENAME;
@@ -49,7 +41,6 @@
 import org.opends.server.schema.AciSyntax;
 import org.opends.server.schema.SubtreeSpecificationSyntax;
 import org.forgerock.i18n.LocalizableMessage;
-import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1;
 import org.forgerock.i18n.slf4j.LocalizedLogger;
 import org.forgerock.opendj.config.ClassPropertyDefinition;
 import org.forgerock.opendj.config.server.ConfigException;
@@ -83,8 +74,6 @@
 import org.opends.server.util.ActivateOnceSDKSchemaIsUsed;
 import org.opends.server.util.StaticUtils;
 
-import com.forgerock.opendj.util.SubstringReader;
-
 /**
  * Responsible for loading the server schema.
  * <p>
@@ -294,88 +283,6 @@
     return extraAttributes.values();
   }
 
-  /**
-   * Adds the provided schema file to the provided schema element definition.
-   *
-   * @param definition
-   *            The schema element definition
-   * @param schemaFile
-   *            The name of the schema file to include in the definition
-   * @return  The definition string of the element
-   *          including the X-SCHEMA-FILE extension.
-   */
-  public static String addSchemaFileToElementDefinitionIfAbsent(String definition, String schemaFile)
-  {
-    if (schemaFile != null && !definition.contains(SCHEMA_PROPERTY_FILENAME))
-    {
-      int pos = definition.lastIndexOf(')');
-      return definition.substring(0, pos).trim() + " " + SCHEMA_PROPERTY_FILENAME + " '" + schemaFile + "' )";
-    }
-    return definition;
-  }
-
-  /**
-   * Parses the schema file (value of X-SCHEMA-FILE extension) from the provided schema element
-   * definition.
-   * <p>
-   * It expects a single value for the X-SCHEMA-FILE extension, e.g.:
-   * "X-SCHEMA-FILE '99-user.ldif'", as there is no sensible meaning for multiple values.
-   *
-   * @param definition
-   *          The definition of a schema element
-   * @return the value of the schema file or {@code null} if the X-SCHEMA-FILE extension is not
-   *         present in the definition
-   * @throws DirectoryException
-   *            If an error occurs while parsing the schema element definition
-   */
-  public static String parseSchemaFileFromElementDefinition(String definition) throws DirectoryException
-  {
-    int pos = definition.lastIndexOf(SCHEMA_PROPERTY_FILENAME);
-    if (pos == -1)
-    {
-      return null;
-    }
-
-    SubstringReader reader = new SubstringReader(definition);
-    reader.read(pos + SCHEMA_PROPERTY_FILENAME.length());
-
-    int length = 0;
-    reader.skipWhitespaces();
-    reader.mark();
-    try
-    {
-      // Accept both a quoted value or an unquoted value
-      char c = reader.read();
-      if (c == '\'')
-      {
-        reader.mark();
-        // Parse until the closing quote.
-        while (reader.read() != '\'')
-        {
-          length++;
-        }
-      }
-      else
-      {
-        // Parse until the next space.
-        do
-        {
-          length++;
-        }
-        while (reader.read() != ' ');
-      }
-      reader.reset();
-      return reader.read(length);
-    }
-    catch (final StringIndexOutOfBoundsException e)
-    {
-      // TODO : write the correct message = Error when trying to parse the schema file from a schema
-      // element definition
-      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, LocalizableMessage.raw(""));
-    }
-  }
-
-
   /** Takes an exclusive lock on the schema. */
   public void exclusiveLock()
   {
@@ -765,150 +672,4 @@
      */
     Schema update(SchemaBuilder builder) throws DirectoryException;
   }
-
-  /**
-   * Returns the OID from the provided attribute type definition, assuming the definition is valid.
-   * <p>
-   * This method does not perform any check.
-   *
-   * @param definition
-   *          The definition of an attribute type, assumed to be valid
-   * @return the OID, which is never {@code null}
-   * @throws DirectoryException
-   *           If a problem occurs while parsing the definition
-   */
-  public static String parseAttributeTypeOID(String definition) throws DirectoryException
-  {
-    return parseOID(definition, ERR_PARSING_ATTRIBUTE_TYPE_OID);
-  }
-
-  /**
-   * Returns the OID from the provided object class definition, assuming the definition is valid.
-   * <p>
-   * This method does not perform any check.
-   *
-   * @param definition
-   *          The definition of a object class, assumed to be valid
-   * @return the OID, which is never {@code null}
-   * @throws DirectoryException
-   *           If a problem occurs while parsing the definition
-   */
-  public static String parseObjectClassOID(String definition) throws DirectoryException
-  {
-    return parseOID(definition, ERR_PARSING_OBJECTCLASS_OID);
-  }
-
-  /**
-   * Returns the OID from the provided name form definition, assuming the definition is valid.
-   * <p>
-   * This method does not perform any check.
-   *
-   * @param definition
-   *          The definition of a name form, assumed to be valid
-   * @return the OID, which is never {@code null}
-   * @throws DirectoryException
-   *           If a problem occurs while parsing the definition
-   */
-  public static String parseNameFormOID(String definition) throws DirectoryException
-  {
-    return parseOID(definition, ERR_PARSING_NAME_FORM_OID);
-  }
-
-  /**
-   * Returns the OID from the provided DIT content rule definition, assuming the definition is valid.
-   * <p>
-   * This method does not perform any check.
-   *
-   * @param definition
-   *          The definition of a DIT content rule, assumed to be valid
-   * @return the OID, which is never {@code null}
-   * @throws DirectoryException
-   *           If a problem occurs while parsing the definition
-   */
-  public static String parseDITContentRuleOID(String definition) throws DirectoryException
-  {
-    return parseOID(definition, ERR_PARSING_DIT_CONTENT_RULE_OID);
-  }
-
-  /**
-   * Returns the ruleID from the provided DIT structure rule definition, assuming the definition is
-   * valid.
-   * <p>
-   * This method does not perform any check.
-   *
-   * @param definition
-   *          The definition of a DIT structure rule, assumed to be valid
-   * @return the OID, which is never {@code null}
-   * @throws DirectoryException
-   *           If a problem occurs while parsing the definition
-   */
-  public static int parseRuleID(String definition) throws DirectoryException
-  {
-    // Reuse code of parseOID, even though this is not an OID
-    return Integer.parseInt(parseOID(definition, ERR_PARSING_DIT_STRUCTURE_RULE_RULEID));
-  }
-
-  /**
-   * Returns the OID from the provided matching rule use definition, assuming the definition is valid.
-   * <p>
-   * This method does not perform any check.
-   *
-   * @param definition
-   *          The definition of a matching rule use, assumed to be valid
-   * @return the OID, which is never {@code null}
-   * @throws DirectoryException
-   *           If a problem occurs while parsing the definition
-   */
-  public static String parseMatchingRuleUseOID(String definition) throws DirectoryException
-  {
-    return parseOID(definition, ERR_PARSING_MATCHING_RULE_USE_OID);
-  }
-
-  /**
-   * Returns the OID from the provided syntax definition, assuming the definition is valid.
-   * <p>
-   * This method does not perform any check.
-   *
-   * @param definition
-   *          The definition of a syntax, assumed to be valid
-   * @return the OID, which is never {@code null}
-   * @throws DirectoryException
-   *           If a problem occurs while parsing the definition
-   */
-  public static String parseSyntaxOID(String definition) throws DirectoryException
-  {
-    return parseOID(definition, ERR_PARSING_LDAP_SYNTAX_OID);
-  }
-
-  private static String parseOID(String definition, Arg1<Object> parsingErrorMsg) throws DirectoryException
-  {
-    try
-    {
-      int pos = 0;
-      int length = definition.length();
-      // Skip over any leading whitespace.
-      while (pos < length && (definition.charAt(pos) == ' '))
-      {
-        pos++;
-      }
-      // Skip the open parenthesis.
-      pos++;
-      // Skip over any spaces immediately following the opening parenthesis.
-      while (pos < length && definition.charAt(pos) == ' ')
-      {
-        pos++;
-      }
-      // The next set of characters must be the OID.
-      int oidStartPos = pos;
-      while (pos < length && definition.charAt(pos) != ' ' && definition.charAt(pos) != ')')
-      {
-        pos++;
-      }
-      return definition.substring(oidStartPos, pos);
-    }
-    catch (IndexOutOfBoundsException e)
-    {
-      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, parsingErrorMsg.get(definition), e);
-    }
-  }
 }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/tasks/AddSchemaFileTask.java b/opendj-server-legacy/src/main/java/org/opends/server/tasks/AddSchemaFileTask.java
index 2fef56a..1548902 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/tasks/AddSchemaFileTask.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/tasks/AddSchemaFileTask.java
@@ -44,6 +44,8 @@
 import org.opends.server.types.Operation;
 import org.opends.server.types.Privilege;
 import org.opends.server.types.Schema;
+import org.opends.server.types.SchemaWriter;
+import org.opends.server.util.SchemaUtils;
 
 import static org.opends.messages.TaskMessages.*;
 import static org.opends.server.config.ConfigConstants.*;
@@ -181,7 +183,7 @@
             AttributeBuilder builder = new AttributeBuilder(a.getAttributeDescription());
             for (ByteString v : a)
             {
-              builder.add(Schema.addSchemaFileToElementDefinitionIfAbsent(v.toString(), schemaFile));
+              builder.add(SchemaUtils.addSchemaFileToElementDefinitionIfAbsent(v.toString(), schemaFile));
             }
 
             mods.add(new Modification(m.getModificationType(), builder.toAttribute()));
@@ -213,7 +215,7 @@
           }
         }
 
-        Schema.writeConcatenatedSchema();
+        SchemaWriter.writeConcatenatedSchema();
       }
 
       schema.setYoungestModificationTime(System.currentTimeMillis());
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeTasks.java b/opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeTasks.java
index 2059687..90f8866 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeTasks.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeTasks.java
@@ -15,17 +15,17 @@
  */
 package org.opends.server.tools.upgrade;
 
+import static org.opends.server.util.SchemaUtils.addSchemaFileToElementDefinitionIfAbsent;
+
 import static java.nio.charset.StandardCharsets.*;
 import static java.nio.file.StandardOpenOption.*;
 import static javax.security.auth.callback.ConfirmationCallback.NO;
 import static javax.security.auth.callback.ConfirmationCallback.YES;
 import static javax.security.auth.callback.TextOutputCallback.*;
-
 import static org.forgerock.util.Utils.joinAsString;
 import static org.opends.messages.ToolMessages.*;
 import static org.opends.server.tools.upgrade.FileManager.copyRecursively;
 import static org.opends.server.tools.upgrade.UpgradeUtils.*;
-import static org.opends.server.types.Schema.*;
 import static org.opends.server.util.StaticUtils.*;
 
 import java.io.BufferedWriter;
@@ -67,6 +67,7 @@
 import org.opends.server.tools.RebuildIndex;
 import org.opends.server.util.BuildVersion;
 import org.opends.server.util.ChangeOperationType;
+import org.opends.server.util.SchemaUtils;
 import org.opends.server.util.StaticUtils;
 
 import com.forgerock.opendj.cli.ClientException;
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/types/Schema.java b/opendj-server-legacy/src/main/java/org/opends/server/types/Schema.java
index 95b87d2..13e6d60 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/types/Schema.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/types/Schema.java
@@ -16,32 +16,14 @@
  */
 package org.opends.server.types;
 
-import static org.forgerock.opendj.ldap.ModificationType.*;
 import static org.forgerock.opendj.ldap.schema.CoreSchema.*;
 import static org.forgerock.opendj.ldap.schema.SchemaOptions.*;
-import static org.opends.messages.BackendMessages.*;
 import static org.opends.messages.SchemaMessages.*;
-import static org.opends.server.config.ConfigConstants.*;
 import static org.opends.server.util.ServerConstants.*;
-import static org.opends.server.util.StaticUtils.*;
 
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.text.ParseException;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
 import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -49,9 +31,6 @@
 import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1;
 import org.forgerock.i18n.LocalizedIllegalArgumentException;
 import org.forgerock.i18n.slf4j.LocalizedLogger;
-import org.forgerock.opendj.ldap.AttributeDescription;
-import org.forgerock.opendj.ldap.ByteString;
-import org.forgerock.opendj.ldap.ModificationType;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.schema.AttributeType;
 import org.forgerock.opendj.ldap.schema.ConflictingSchemaElementException;
@@ -68,8 +47,6 @@
 import org.forgerock.util.Option;
 import org.forgerock.util.Utils;
 import org.opends.server.core.DirectoryServer;
-import org.opends.server.core.SchemaConfigManager;
-import org.opends.server.util.Base64;
 
 /**
  * This class defines a data structure that holds information about the components of the Directory
@@ -1551,357 +1528,6 @@
     extraAttributes.put(name, attr);
   }
 
-
-  /**
-   * Writes a single file containing all schema element definitions,
-   * which can be used on startup to determine whether the schema
-   * files were edited with the server offline.
-   */
-  public static void writeConcatenatedSchema()
-  {
-    String concatFilePath = null;
-    try
-    {
-      Set<String> attributeTypes = new LinkedHashSet<>();
-      Set<String> objectClasses = new LinkedHashSet<>();
-      Set<String> nameForms = new LinkedHashSet<>();
-      Set<String> ditContentRules = new LinkedHashSet<>();
-      Set<String> ditStructureRules = new LinkedHashSet<>();
-      Set<String> matchingRuleUses = new LinkedHashSet<>();
-      Set<String> ldapSyntaxes = new LinkedHashSet<>();
-      genConcatenatedSchema(attributeTypes, objectClasses, nameForms,
-                            ditContentRules, ditStructureRules,
-                            matchingRuleUses,ldapSyntaxes);
-
-
-      File configFile = new File(DirectoryServer.getConfigFile());
-      File configDirectory = configFile.getParentFile();
-      File upgradeDirectory = new File(configDirectory, "upgrade");
-      upgradeDirectory.mkdir();
-      File concatFile = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME);
-      concatFilePath = concatFile.getAbsolutePath();
-
-      File tempFile = new File(concatFilePath + ".tmp");
-      try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile, false)))
-      {
-        writeLines(writer,
-            "dn: " + DirectoryServer.getSchemaDN(),
-            "objectClass: top",
-            "objectClass: ldapSubentry",
-            "objectClass: subschema");
-
-        writeLines(writer, ATTR_ATTRIBUTE_TYPES, attributeTypes);
-        writeLines(writer, ATTR_OBJECTCLASSES, objectClasses);
-        writeLines(writer, ATTR_NAME_FORMS, nameForms);
-        writeLines(writer, ATTR_DIT_CONTENT_RULES, ditContentRules);
-        writeLines(writer, ATTR_DIT_STRUCTURE_RULES, ditStructureRules);
-        writeLines(writer, ATTR_MATCHING_RULE_USE, matchingRuleUses);
-        writeLines(writer, ATTR_LDAP_SYNTAXES, ldapSyntaxes);
-      }
-
-      if (concatFile.exists())
-      {
-        concatFile.delete();
-      }
-      tempFile.renameTo(concatFile);
-    }
-    catch (Exception e)
-    {
-      logger.traceException(e);
-
-      // This is definitely not ideal, but it's not the end of the
-      // world.  The worst that should happen is that the schema
-      // changes could potentially be sent to the other servers again
-      // when this server is restarted, which shouldn't hurt anything.
-      // Still, we should log a warning message.
-      logger.error(ERR_SCHEMA_CANNOT_WRITE_CONCAT_SCHEMA_FILE, concatFilePath, getExceptionMessage(e));
-    }
-  }
-
-  private static void writeLines(BufferedWriter writer, String... lines) throws IOException
-  {
-    for (String line : lines)
-    {
-      writer.write(line);
-      writer.newLine();
-    }
-  }
-
-  private static void writeLines(BufferedWriter writer, String beforeColumn, Set<String> lines) throws IOException
-  {
-    for (String line : lines)
-    {
-      writer.write(beforeColumn);
-      writer.write(": ");
-      writer.write(line);
-      writer.newLine();
-    }
-  }
-
-  /**
-   * Reads the files contained in the schema directory and generates a
-   * concatenated view of their contents in the provided sets.
-   *
-   * @param  attributeTypes     The set into which to place the
-   *                            attribute types read from the schema
-   *                            files.
-   * @param  objectClasses      The set into which to place the object
-   *                            classes read from the schema files.
-   * @param  nameForms          The set into which to place the name
-   *                            forms read from the schema files.
-   * @param  ditContentRules    The set into which to place the DIT
-   *                            content rules read from the schema
-   *                            files.
-   * @param  ditStructureRules  The set into which to place the DIT
-   *                            structure rules read from the schema
-   *                            files.
-   * @param  matchingRuleUses   The set into which to place the
-   *                            matching rule uses read from the
-   *                            schema files.
-   * @param ldapSyntaxes The set into which to place the
-   *                            ldap syntaxes read from the
-   *                            schema files.
-   *
-   * @throws  IOException  If a problem occurs while reading the
-   *                       schema file elements.
-   */
-  public static void genConcatenatedSchema(
-                          Set<String> attributeTypes,
-                          Set<String> objectClasses,
-                          Set<String> nameForms,
-                          Set<String> ditContentRules,
-                          Set<String> ditStructureRules,
-                          Set<String> matchingRuleUses,
-                          Set<String> ldapSyntaxes)
-          throws IOException
-  {
-    // Get a sorted list of the files in the schema directory.
-    TreeSet<File> schemaFiles = new TreeSet<>();
-    String schemaDirectory = SchemaConfigManager.getSchemaDirectoryPath();
-
-    final FilenameFilter filter = new SchemaConfigManager.SchemaFileFilter();
-    for (File f : new File(schemaDirectory).listFiles(filter))
-    {
-      if (f.isFile())
-      {
-        schemaFiles.add(f);
-      }
-    }
-
-
-    // Open each of the files in order and read the elements that they
-    // contain, appending them to the appropriate lists.
-    for (File f : schemaFiles)
-    {
-      List<StringBuilder> lines = readSchemaElementsFromLdif(f);
-
-      // Iterate through each line in the list.  Find the colon and
-      // get the attribute name at the beginning.  If it's something
-      // that we don't recognize, then skip it.  Otherwise, add the
-      // X-SCHEMA-FILE extension and add it to the appropriate schema
-      // element list.
-      for (StringBuilder buffer : lines)
-      {
-        String line = buffer.toString().trim();
-        parseSchemaLine(line, f.getName(), attributeTypes, objectClasses,
-            nameForms, ditContentRules, ditStructureRules, matchingRuleUses,
-            ldapSyntaxes);
-      }
-    }
-  }
-
-  private static List<StringBuilder> readSchemaElementsFromLdif(File f) throws IOException, FileNotFoundException
-  {
-    final LinkedList<StringBuilder> lines = new LinkedList<>();
-
-    try (BufferedReader reader = new BufferedReader(new FileReader(f)))
-    {
-      String line;
-      while ((line = reader.readLine()) != null)
-      {
-        if (line.startsWith("#") || line.length() == 0)
-        {
-          continue;
-        }
-        else if (line.startsWith(" "))
-        {
-          lines.getLast().append(line.substring(1));
-        }
-        else
-        {
-          lines.add(new StringBuilder(line));
-        }
-      }
-    }
-    return lines;
-  }
-
-
-
-  /**
-   * Reads data from the specified concatenated schema file into the
-   * provided sets.
-   *
-   * @param  concatSchemaFile   The concatenated schema file to be read.
-   * @param  attributeTypes     The set into which to place the attribute types
-   *                            read from the concatenated schema file.
-   * @param  objectClasses      The set into which to place the object classes
-   *                            read from the concatenated schema file.
-   * @param  nameForms          The set into which to place the name forms
-   *                            read from the concatenated schema file.
-   * @param  ditContentRules    The set into which to place the DIT content rules
-   *                            read from the concatenated schema file.
-   * @param  ditStructureRules  The set into which to place the DIT structure rules
-   *                            read from the concatenated schema file.
-   * @param  matchingRuleUses   The set into which to place the matching rule
-   *                            uses read from the concatenated schema file.
-   * @param ldapSyntaxes        The set into which to place the ldap syntaxes
-   *                            read from the concatenated schema file.
-   *
-   * @throws  IOException  If a problem occurs while reading the
-   *                       schema file elements.
-   */
-  public static void readConcatenatedSchema(File concatSchemaFile,
-                          Set<String> attributeTypes,
-                          Set<String> objectClasses,
-                          Set<String> nameForms,
-                          Set<String> ditContentRules,
-                          Set<String> ditStructureRules,
-                          Set<String> matchingRuleUses,
-                          Set<String> ldapSyntaxes)
-          throws IOException
-  {
-    try (BufferedReader reader = new BufferedReader(new FileReader(concatSchemaFile)))
-    {
-      String line;
-      while ((line = reader.readLine()) != null)
-      {
-        parseSchemaLine(line, null, attributeTypes, objectClasses,
-            nameForms, ditContentRules, ditStructureRules, matchingRuleUses,
-            ldapSyntaxes);
-      }
-    }
-  }
-
-  private static void parseSchemaLine(String definition, String fileName,
-      Set<String> attributeTypes,
-      Set<String> objectClasses,
-      Set<String> nameForms,
-      Set<String> ditContentRules,
-      Set<String> ditStructureRules,
-      Set<String> matchingRuleUses,
-      Set<String> ldapSyntaxes)
-  {
-    String lowerLine = toLowerCase(definition);
-
-    try
-    {
-      if (lowerLine.startsWith(ATTR_ATTRIBUTE_TYPES_LC))
-      {
-        addSchemaDefinition(attributeTypes, definition, ATTR_ATTRIBUTE_TYPES_LC, fileName);
-      }
-      else if (lowerLine.startsWith(ATTR_OBJECTCLASSES_LC))
-      {
-        addSchemaDefinition(objectClasses, definition, ATTR_OBJECTCLASSES_LC, fileName);
-      }
-      else if (lowerLine.startsWith(ATTR_NAME_FORMS_LC))
-      {
-        addSchemaDefinition(nameForms, definition, ATTR_NAME_FORMS_LC, fileName);
-      }
-      else if (lowerLine.startsWith(ATTR_DIT_CONTENT_RULES_LC))
-      {
-        addSchemaDefinition(ditContentRules, definition, ATTR_DIT_CONTENT_RULES_LC, fileName);
-      }
-      else if (lowerLine.startsWith(ATTR_DIT_STRUCTURE_RULES_LC))
-      {
-        addSchemaDefinition(ditStructureRules, definition, ATTR_DIT_STRUCTURE_RULES_LC, fileName);
-      }
-      else if (lowerLine.startsWith(ATTR_MATCHING_RULE_USE_LC))
-      {
-        addSchemaDefinition(matchingRuleUses, definition, ATTR_MATCHING_RULE_USE_LC, fileName);
-      }
-      else if (lowerLine.startsWith(ATTR_LDAP_SYNTAXES_LC))
-      {
-        addSchemaDefinition(ldapSyntaxes, definition, ATTR_LDAP_SYNTAXES_LC, fileName);
-      }
-    } catch (ParseException pe)
-    {
-      logger.error(ERR_SCHEMA_PARSE_LINE.get(definition, pe.getLocalizedMessage()));
-    }
-  }
-
-  private static void addSchemaDefinition(Set<String> definitions, String line, String attrName, String fileName)
-      throws ParseException
-  {
-    definitions.add(getSchemaDefinition(line.substring(attrName.length()), fileName));
-  }
-
-  private static String getSchemaDefinition(String definition, String schemaFile) throws ParseException
-  {
-    if (definition.startsWith("::"))
-    {
-      // See OPENDJ-2792: the definition of the ds-cfg-csv-delimiter-char attribute type
-      // had a space accidentally added after the closing parenthesis.
-      // This was unfortunately interpreted as base64
-      definition = ByteString.wrap(Base64.decode(definition.substring(2).trim())).toString();
-    }
-    else if (definition.startsWith(":"))
-    {
-      definition = definition.substring(1).trim();
-    }
-    else
-    {
-      throw new ParseException(ERR_SCHEMA_COULD_NOT_PARSE_DEFINITION.get().toString(), 0);
-    }
-
-    return addSchemaFileToElementDefinitionIfAbsent(definition, schemaFile);
-  }
-
-  /**
-   * Compares the provided sets of schema element definitions and
-   * writes any differences found into the given list of
-   * modifications.
-   *
-   * @param  oldElements  The set of elements of the specified type
-   *                      read from the previous concatenated schema
-   *                      files.
-   * @param  newElements  The set of elements of the specified type
-   *                      read from the server's current schema.
-   * @param  elementType  The attribute type associated with the
-   *                      schema element being compared.
-   * @param  mods         The list of modifications into which any
-   *                      identified differences should be written.
-   */
-  public static void compareConcatenatedSchema(
-                          Set<String> oldElements,
-                          Set<String> newElements,
-                          AttributeType elementType,
-                          List<Modification> mods)
-  {
-    AttributeBuilder builder = new AttributeBuilder(elementType);
-    addModification(mods, DELETE, oldElements, newElements, builder);
-
-    builder.setAttributeDescription(AttributeDescription.create(elementType));
-    addModification(mods, ADD, newElements, oldElements, builder);
-  }
-
-  private static void addModification(List<Modification> mods, ModificationType modType, Set<String> included,
-      Set<String> excluded, AttributeBuilder builder)
-  {
-    for (String val : included)
-    {
-      if (!excluded.contains(val))
-      {
-        builder.add(val);
-      }
-    }
-
-    if (!builder.isEmpty())
-    {
-      mods.add(new Modification(modType, builder.toAttribute()));
-    }
-  }
-
   /**
    * Destroys the structures maintained by the schema so that they are
    * no longer usable. This should only be called at the end of the
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/types/SchemaWriter.java b/opendj-server-legacy/src/main/java/org/opends/server/types/SchemaWriter.java
new file mode 100644
index 0000000..efeafd4
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/opends/server/types/SchemaWriter.java
@@ -0,0 +1,486 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2016 ForgeRock AS.
+ */
+package org.opends.server.types;
+
+import static org.opends.messages.BackendMessages.ERR_SCHEMA_CANNOT_FIND_CONCAT_FILE;
+import static org.opends.server.util.ServerConstants.*;
+
+import static org.opends.messages.BackendMessages.ERR_SCHEMA_COULD_NOT_PARSE_DEFINITION;
+import static org.opends.messages.BackendMessages.ERR_SCHEMA_PARSE_LINE;
+import static org.opends.server.config.ConfigConstants.*;
+import static org.opends.messages.BackendMessages.ERR_SCHEMA_ERROR_DETERMINING_SCHEMA_CHANGES;
+import static org.forgerock.opendj.ldap.schema.CoreSchema.*;
+import static org.forgerock.opendj.ldap.ModificationType.ADD;
+import static org.forgerock.opendj.ldap.ModificationType.DELETE;
+import static org.opends.messages.BackendMessages.*;
+import static org.opends.server.config.ConfigConstants.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+import static org.opends.server.util.StaticUtils.toLowerCase;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.opendj.ldap.AttributeDescription;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ModificationType;
+import org.forgerock.opendj.ldap.schema.AttributeType;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.SchemaConfigManager;
+import org.opends.server.util.Base64;
+import org.opends.server.util.BuildVersion;
+import org.opends.server.util.SchemaUtils;
+
+/**
+ * Provides support to write schema files.
+ */
+public class SchemaWriter
+{
+  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+
+  private static final AttributeType attributeTypesType = getAttributeTypesAttributeType();
+  private static final AttributeType ditStructureRulesType = getDITStructureRulesAttributeType();
+  private static final AttributeType ditContentRulesType = getDITContentRulesAttributeType();
+  private static final AttributeType ldapSyntaxesType = getLDAPSyntaxesAttributeType();
+  private static final AttributeType matchingRuleUsesType = getMatchingRuleUseAttributeType();
+  private static final AttributeType nameFormsType = getNameFormsAttributeType();
+  private static final AttributeType objectClassesType = getObjectClassesAttributeType();
+
+  /**
+   * Compares the provided sets of schema element definitions and writes any differences found into
+   * the given list of modifications.
+   *
+   * @param oldElements
+   *          The set of elements of the specified type read from the previous concatenated schema
+   *          files.
+   * @param newElements
+   *          The set of elements of the specified type read from the server's current schema.
+   * @param elementType
+   *          The attribute type associated with the schema element being compared.
+   * @param mods
+   *          The list of modifications into which any identified differences should be written.
+   */
+  public static void compareConcatenatedSchema(Set<String> oldElements, Set<String> newElements,
+      AttributeType elementType, List<Modification> mods)
+  {
+    AttributeBuilder builder = new AttributeBuilder(elementType);
+    addModification(mods, DELETE, oldElements, newElements, builder);
+
+    builder.setAttributeDescription(AttributeDescription.create(elementType));
+    addModification(mods, ADD, newElements, oldElements, builder);
+  }
+
+  /**
+   * Reads the files contained in the schema directory and generates a concatenated view of their
+   * contents in the provided sets.
+   *
+   * @param attributeTypes
+   *          The set into which to place the attribute types read from the schema files.
+   * @param objectClasses
+   *          The set into which to place the object classes read from the schema files.
+   * @param nameForms
+   *          The set into which to place the name forms read from the schema files.
+   * @param ditContentRules
+   *          The set into which to place the DIT content rules read from the schema files.
+   * @param ditStructureRules
+   *          The set into which to place the DIT structure rules read from the schema files.
+   * @param matchingRuleUses
+   *          The set into which to place the matching rule uses read from the schema files.
+   * @param ldapSyntaxes
+   *          The set into which to place the ldap syntaxes read from the schema files.
+   * @throws IOException
+   *           If a problem occurs while reading the schema file elements.
+   */
+  public static void generateConcatenatedSchema(Set<String> attributeTypes, Set<String> objectClasses,
+      Set<String> nameForms, Set<String> ditContentRules, Set<String> ditStructureRules, Set<String> matchingRuleUses,
+      Set<String> ldapSyntaxes) throws IOException
+  {
+    // Get a sorted list of the files in the schema directory.
+    TreeSet<File> schemaFiles = new TreeSet<>();
+    String schemaDirectory = SchemaConfigManager.getSchemaDirectoryPath();
+
+    final FilenameFilter filter = new SchemaConfigManager.SchemaFileFilter();
+    for (File f : new File(schemaDirectory).listFiles(filter))
+    {
+      if (f.isFile())
+      {
+        schemaFiles.add(f);
+      }
+    }
+
+    // Open each of the files in order and read the elements that they
+    // contain, appending them to the appropriate lists.
+    for (File f : schemaFiles)
+    {
+      List<StringBuilder> lines = readSchemaElementsFromLdif(f);
+
+      // Iterate through each line in the list. Find the colon and
+      // get the attribute name at the beginning. If it's something
+      // that we don't recognize, then skip it. Otherwise, add the
+      // X-SCHEMA-FILE extension and add it to the appropriate schema
+      // element list.
+      for (StringBuilder buffer : lines)
+      {
+        String line = buffer.toString().trim();
+        parseSchemaLine(line, f.getName(), attributeTypes, objectClasses, nameForms, ditContentRules,
+            ditStructureRules, matchingRuleUses, ldapSyntaxes);
+      }
+    }
+  }
+
+  /**
+   * Reads data from the specified concatenated schema file into the provided sets.
+   *
+   * @param concatSchemaFile
+   *          The concatenated schema file to be read.
+   * @param attributeTypes
+   *          The set into which to place the attribute types read from the concatenated schema
+   *          file.
+   * @param objectClasses
+   *          The set into which to place the object classes read from the concatenated schema file.
+   * @param nameForms
+   *          The set into which to place the name forms read from the concatenated schema file.
+   * @param ditContentRules
+   *          The set into which to place the DIT content rules read from the concatenated schema
+   *          file.
+   * @param ditStructureRules
+   *          The set into which to place the DIT structure rules read from the concatenated schema
+   *          file.
+   * @param matchingRuleUses
+   *          The set into which to place the matching rule uses read from the concatenated schema
+   *          file.
+   * @param ldapSyntaxes
+   *          The set into which to place the ldap syntaxes read from the concatenated schema file.
+   * @throws IOException
+   *           If a problem occurs while reading the schema file elements.
+   */
+  public static void readConcatenatedSchema(File concatSchemaFile, Set<String> attributeTypes,
+      Set<String> objectClasses, Set<String> nameForms, Set<String> ditContentRules, Set<String> ditStructureRules,
+      Set<String> matchingRuleUses, Set<String> ldapSyntaxes) throws IOException
+  {
+    try (BufferedReader reader = new BufferedReader(new FileReader(concatSchemaFile)))
+    {
+      String line;
+      while ((line = reader.readLine()) != null)
+      {
+        parseSchemaLine(line, null, attributeTypes, objectClasses, nameForms, ditContentRules, ditStructureRules,
+            matchingRuleUses, ldapSyntaxes);
+      }
+    }
+  }
+
+  /**
+   * Updates the concatenated schema if changes are detected in the current schema files.
+   * <p>
+   * Identify any differences that may exist between the concatenated schema file from the last
+   * online modification and the current schema files. If there are any differences, then they
+   * should be from making changes to the schema files with the server offline.
+   */
+  public static void updateConcatenatedSchema() throws InitializationException
+  {
+    try
+    {
+      // First, generate lists of elements from the current schema.
+      Set<String> newATs = new LinkedHashSet<>();
+      Set<String> newOCs = new LinkedHashSet<>();
+      Set<String> newNFs = new LinkedHashSet<>();
+      Set<String> newDCRs = new LinkedHashSet<>();
+      Set<String> newDSRs = new LinkedHashSet<>();
+      Set<String> newMRUs = new LinkedHashSet<>();
+      Set<String> newLSs = new LinkedHashSet<>();
+      generateConcatenatedSchema(newATs, newOCs, newNFs, newDCRs, newDSRs, newMRUs, newLSs);
+
+      // Next, generate lists of elements from the previous concatenated schema.
+      // If there isn't a previous concatenated schema, then use the base
+      // schema for the current revision.
+      File concatFile = getConcatenatedSchemaFile();
+
+      Set<String> oldATs = new LinkedHashSet<>();
+      Set<String> oldOCs = new LinkedHashSet<>();
+      Set<String> oldNFs = new LinkedHashSet<>();
+      Set<String> oldDCRs = new LinkedHashSet<>();
+      Set<String> oldDSRs = new LinkedHashSet<>();
+      Set<String> oldMRUs = new LinkedHashSet<>();
+      Set<String> oldLSs = new LinkedHashSet<>();
+      readConcatenatedSchema(concatFile, oldATs, oldOCs, oldNFs, oldDCRs, oldDSRs, oldMRUs, oldLSs);
+
+      // Create a list of modifications and add any differences between the old
+      // and new schema into them.
+      List<Modification> mods = new LinkedList<>();
+      compareConcatenatedSchema(oldATs, newATs, attributeTypesType, mods);
+      compareConcatenatedSchema(oldOCs, newOCs, objectClassesType, mods);
+      compareConcatenatedSchema(oldNFs, newNFs, nameFormsType, mods);
+      compareConcatenatedSchema(oldDCRs, newDCRs, ditContentRulesType, mods);
+      compareConcatenatedSchema(oldDSRs, newDSRs, ditStructureRulesType, mods);
+      compareConcatenatedSchema(oldMRUs, newMRUs, matchingRuleUsesType, mods);
+      compareConcatenatedSchema(oldLSs, newLSs, ldapSyntaxesType, mods);
+      if (!mods.isEmpty())
+      {
+        // TODO : Raise an alert notification.
+
+        DirectoryServer.setOfflineSchemaChanges(mods);
+
+        // Write a new concatenated schema file with the most recent information
+        // so we don't re-find these same changes on the next startup.
+        writeConcatenatedSchema();
+      }
+    }
+    catch (InitializationException ie)
+    {
+      throw ie;
+    }
+    catch (Exception e)
+    {
+      logger.traceException(e);
+
+      logger.error(ERR_SCHEMA_ERROR_DETERMINING_SCHEMA_CHANGES, getExceptionMessage(e));
+    }
+  }
+
+  /**
+   * Writes a single file containing all schema element definitions, which can be used on startup to
+   * determine whether the schema files were edited with the server offline.
+   */
+  public static void writeConcatenatedSchema()
+  {
+    String concatFilePath = null;
+    try
+    {
+      Set<String> attributeTypes = new LinkedHashSet<>();
+      Set<String> objectClasses = new LinkedHashSet<>();
+      Set<String> nameForms = new LinkedHashSet<>();
+      Set<String> ditContentRules = new LinkedHashSet<>();
+      Set<String> ditStructureRules = new LinkedHashSet<>();
+      Set<String> matchingRuleUses = new LinkedHashSet<>();
+      Set<String> ldapSyntaxes = new LinkedHashSet<>();
+      generateConcatenatedSchema(attributeTypes, objectClasses, nameForms, ditContentRules, ditStructureRules,
+          matchingRuleUses, ldapSyntaxes);
+
+      File configFile = new File(DirectoryServer.getConfigFile());
+      File configDirectory = configFile.getParentFile();
+      File upgradeDirectory = new File(configDirectory, "upgrade");
+      upgradeDirectory.mkdir();
+      File concatFile = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME);
+      concatFilePath = concatFile.getAbsolutePath();
+
+      File tempFile = new File(concatFilePath + ".tmp");
+      try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile, false)))
+      {
+        writeLines(writer, "dn: " + DirectoryServer.getSchemaDN(), "objectClass: top", "objectClass: ldapSubentry",
+            "objectClass: subschema");
+
+        writeLines(writer, ATTR_ATTRIBUTE_TYPES, attributeTypes);
+        writeLines(writer, ATTR_OBJECTCLASSES, objectClasses);
+        writeLines(writer, ATTR_NAME_FORMS, nameForms);
+        writeLines(writer, ATTR_DIT_CONTENT_RULES, ditContentRules);
+        writeLines(writer, ATTR_DIT_STRUCTURE_RULES, ditStructureRules);
+        writeLines(writer, ATTR_MATCHING_RULE_USE, matchingRuleUses);
+        writeLines(writer, ATTR_LDAP_SYNTAXES, ldapSyntaxes);
+      }
+
+      if (concatFile.exists())
+      {
+        concatFile.delete();
+      }
+      tempFile.renameTo(concatFile);
+    }
+    catch (Exception e)
+    {
+      logger.traceException(e);
+
+      // This is definitely not ideal, but it's not the end of the
+      // world. The worst that should happen is that the schema
+      // changes could potentially be sent to the other servers again
+      // when this server is restarted, which shouldn't hurt anything.
+      // Still, we should log a warning message.
+      logger.error(ERR_SCHEMA_CANNOT_WRITE_CONCAT_SCHEMA_FILE, concatFilePath, getExceptionMessage(e));
+    }
+  }
+
+  private static void addModification(List<Modification> mods, ModificationType modType, Set<String> included,
+      Set<String> excluded, AttributeBuilder builder)
+  {
+    for (String val : included)
+    {
+      if (!excluded.contains(val))
+      {
+        builder.add(val);
+      }
+    }
+
+    if (!builder.isEmpty())
+    {
+      mods.add(new Modification(modType, builder.toAttribute()));
+    }
+  }
+
+  private static void addSchemaDefinition(Set<String> definitions, String line, String attrName, String fileName)
+      throws ParseException
+  {
+    definitions.add(getSchemaDefinition(line.substring(attrName.length()), fileName));
+  }
+
+  private static File getConcatenatedSchemaFile() throws InitializationException
+  {
+    File configDirectory = new File(DirectoryServer.getConfigFile()).getParentFile();
+    File upgradeDirectory = new File(configDirectory, "upgrade");
+    File concatFile = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME);
+    if (concatFile.exists())
+    {
+      return concatFile.getAbsoluteFile();
+    }
+
+    String fileName = SCHEMA_BASE_FILE_NAME_WITHOUT_REVISION + BuildVersion.instanceVersion().getRevision();
+    concatFile = new File(upgradeDirectory, fileName);
+    if (concatFile.exists())
+    {
+      return concatFile.getAbsoluteFile();
+    }
+
+    String runningUnitTestsStr = System.getProperty(PROPERTY_RUNNING_UNIT_TESTS);
+    if ("true".equalsIgnoreCase(runningUnitTestsStr))
+    {
+      writeConcatenatedSchema();
+      concatFile = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME);
+      return concatFile.getAbsoluteFile();
+    }
+    throw new InitializationException(ERR_SCHEMA_CANNOT_FIND_CONCAT_FILE.get(upgradeDirectory.getAbsolutePath(),
+        SCHEMA_CONCAT_FILE_NAME, concatFile.getName()));
+  }
+
+  private static String getSchemaDefinition(String definition, String schemaFile) throws ParseException
+  {
+    if (definition.startsWith("::"))
+    {
+      // See OPENDJ-2792: the definition of the ds-cfg-csv-delimiter-char attribute type
+      // had a space accidentally added after the closing parenthesis.
+      // This was unfortunately interpreted as base64
+      definition = ByteString.wrap(Base64.decode(definition.substring(2).trim())).toString();
+    }
+    else if (definition.startsWith(":"))
+    {
+      definition = definition.substring(1).trim();
+    }
+    else
+    {
+      throw new ParseException(ERR_SCHEMA_COULD_NOT_PARSE_DEFINITION.get().toString(), 0);
+    }
+
+    return SchemaUtils.addSchemaFileToElementDefinitionIfAbsent(definition, schemaFile);
+  }
+
+  private static void parseSchemaLine(String definition, String fileName, Set<String> attributeTypes,
+      Set<String> objectClasses, Set<String> nameForms, Set<String> ditContentRules, Set<String> ditStructureRules,
+      Set<String> matchingRuleUses, Set<String> ldapSyntaxes)
+  {
+    String lowerLine = toLowerCase(definition);
+
+    try
+    {
+      if (lowerLine.startsWith(ATTR_ATTRIBUTE_TYPES_LC))
+      {
+        addSchemaDefinition(attributeTypes, definition, ATTR_ATTRIBUTE_TYPES_LC, fileName);
+      }
+      else if (lowerLine.startsWith(ATTR_OBJECTCLASSES_LC))
+      {
+        addSchemaDefinition(objectClasses, definition, ATTR_OBJECTCLASSES_LC, fileName);
+      }
+      else if (lowerLine.startsWith(ATTR_NAME_FORMS_LC))
+      {
+        addSchemaDefinition(nameForms, definition, ATTR_NAME_FORMS_LC, fileName);
+      }
+      else if (lowerLine.startsWith(ATTR_DIT_CONTENT_RULES_LC))
+      {
+        addSchemaDefinition(ditContentRules, definition, ATTR_DIT_CONTENT_RULES_LC, fileName);
+      }
+      else if (lowerLine.startsWith(ATTR_DIT_STRUCTURE_RULES_LC))
+      {
+        addSchemaDefinition(ditStructureRules, definition, ATTR_DIT_STRUCTURE_RULES_LC, fileName);
+      }
+      else if (lowerLine.startsWith(ATTR_MATCHING_RULE_USE_LC))
+      {
+        addSchemaDefinition(matchingRuleUses, definition, ATTR_MATCHING_RULE_USE_LC, fileName);
+      }
+      else if (lowerLine.startsWith(ATTR_LDAP_SYNTAXES_LC))
+      {
+        addSchemaDefinition(ldapSyntaxes, definition, ATTR_LDAP_SYNTAXES_LC, fileName);
+      }
+    }
+    catch (ParseException pe)
+    {
+      logger.error(ERR_SCHEMA_PARSE_LINE.get(definition, pe.getLocalizedMessage()));
+    }
+  }
+
+  private static List<StringBuilder> readSchemaElementsFromLdif(File f) throws IOException, FileNotFoundException
+  {
+    final LinkedList<StringBuilder> lines = new LinkedList<>();
+
+    try (BufferedReader reader = new BufferedReader(new FileReader(f)))
+    {
+      String line;
+      while ((line = reader.readLine()) != null)
+      {
+        if (line.startsWith("#") || line.length() == 0)
+        {
+          continue;
+        }
+        else if (line.startsWith(" "))
+        {
+          lines.getLast().append(line.substring(1));
+        }
+        else
+        {
+          lines.add(new StringBuilder(line));
+        }
+      }
+    }
+    return lines;
+  }
+
+  private static void writeLines(BufferedWriter writer, String... lines) throws IOException
+  {
+    for (String line : lines)
+    {
+      writer.write(line);
+      writer.newLine();
+    }
+  }
+
+  private static void writeLines(BufferedWriter writer, String beforeColumn, Set<String> lines) throws IOException
+  {
+    for (String line : lines)
+    {
+      writer.write(beforeColumn);
+      writer.write(": ");
+      writer.write(line);
+      writer.newLine();
+    }
+  }
+}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/util/SchemaUtils.java b/opendj-server-legacy/src/main/java/org/opends/server/util/SchemaUtils.java
index 166b2d0..6f9740e 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/util/SchemaUtils.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/util/SchemaUtils.java
@@ -15,7 +15,9 @@
  */
 package org.opends.server.util;
 
-import static org.opends.server.types.Schema.addSchemaFileToElementDefinitionIfAbsent;
+import static org.opends.messages.SchemaMessages.*;
+
+import static org.opends.server.util.ServerConstants.SCHEMA_PROPERTY_FILENAME;
 import static org.opends.server.schema.SchemaConstants.SYNTAX_AUTH_PASSWORD_OID;
 import static org.opends.server.schema.SchemaConstants.SYNTAX_USER_PASSWORD_OID;
 
@@ -24,12 +26,18 @@
 import java.util.List;
 import java.util.Set;
 
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1;
+import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.schema.AttributeType;
 import org.forgerock.opendj.ldap.schema.ObjectClass;
 import org.forgerock.opendj.ldap.schema.Schema;
 import org.forgerock.opendj.ldap.schema.SchemaBuilder;
 import org.forgerock.opendj.ldap.schema.SchemaElement;
 import org.opends.server.core.ServerContext;
+import org.opends.server.types.DirectoryException;
+
+import com.forgerock.opendj.util.SubstringReader;
 
 /** Utility methods related to schema. */
 public class SchemaUtils
@@ -189,4 +197,244 @@
     }
     return attributeType;
   }
+
+  /**
+   * Adds the provided schema file to the provided schema element definition.
+   *
+   * @param definition
+   *            The schema element definition
+   * @param schemaFile
+   *            The name of the schema file to include in the definition
+   * @return  The definition string of the element
+   *          including the X-SCHEMA-FILE extension.
+   */
+  public static String addSchemaFileToElementDefinitionIfAbsent(String definition, String schemaFile)
+  {
+    if (schemaFile != null && !definition.contains(SCHEMA_PROPERTY_FILENAME))
+    {
+      int pos = definition.lastIndexOf(')');
+      return definition.substring(0, pos).trim() + " " + SCHEMA_PROPERTY_FILENAME + " '" + schemaFile + "' )";
+    }
+    return definition;
+  }
+
+  /**
+   * Parses the schema file (value of X-SCHEMA-FILE extension) from the provided schema element
+   * definition.
+   * <p>
+   * It expects a single value for the X-SCHEMA-FILE extension, e.g.:
+   * "X-SCHEMA-FILE '99-user.ldif'", as there is no sensible meaning for multiple values.
+   *
+   * @param definition
+   *          The definition of a schema element
+   * @return the value of the schema file or {@code null} if the X-SCHEMA-FILE extension is not
+   *         present in the definition
+   * @throws DirectoryException
+   *            If an error occurs while parsing the schema element definition
+   */
+  public static String parseSchemaFileFromElementDefinition(String definition) throws DirectoryException
+  {
+    int pos = definition.lastIndexOf(SCHEMA_PROPERTY_FILENAME);
+    if (pos == -1)
+    {
+      return null;
+    }
+
+    SubstringReader reader = new SubstringReader(definition);
+    reader.read(pos + SCHEMA_PROPERTY_FILENAME.length());
+
+    int length = 0;
+    reader.skipWhitespaces();
+    reader.mark();
+    try
+    {
+      // Accept both a quoted value or an unquoted value
+      char c = reader.read();
+      if (c == '\'')
+      {
+        reader.mark();
+        // Parse until the closing quote.
+        while (reader.read() != '\'')
+        {
+          length++;
+        }
+      }
+      else
+      {
+        // Parse until the next space.
+        do
+        {
+          length++;
+        }
+        while (reader.read() != ' ');
+      }
+      reader.reset();
+      return reader.read(length);
+    }
+    catch (final StringIndexOutOfBoundsException e)
+    {
+      // TODO : write the correct message = Error when trying to parse the schema file from a schema
+      // element definition
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, LocalizableMessage.raw(""));
+    }
+  }
+
+  /**
+   * Returns the OID from the provided attribute type definition, assuming the definition is valid.
+   * <p>
+   * This method does not perform any check.
+   *
+   * @param definition
+   *          The definition of an attribute type, assumed to be valid
+   * @return the OID, which is never {@code null}
+   * @throws DirectoryException
+   *           If a problem occurs while parsing the definition
+   */
+  public static String parseAttributeTypeOID(String definition) throws DirectoryException
+  {
+    return parseOID(definition, ERR_PARSING_ATTRIBUTE_TYPE_OID);
+  }
+
+  /**
+   * Returns the OID from the provided object class definition, assuming the definition is valid.
+   * <p>
+   * This method does not perform any check.
+   *
+   * @param definition
+   *          The definition of a object class, assumed to be valid
+   * @return the OID, which is never {@code null}
+   * @throws DirectoryException
+   *           If a problem occurs while parsing the definition
+   */
+  public static String parseObjectClassOID(String definition) throws DirectoryException
+  {
+    return parseOID(definition, ERR_PARSING_OBJECTCLASS_OID);
+  }
+
+  /**
+   * Returns the OID from the provided name form definition, assuming the definition is valid.
+   * <p>
+   * This method does not perform any check.
+   *
+   * @param definition
+   *          The definition of a name form, assumed to be valid
+   * @return the OID, which is never {@code null}
+   * @throws DirectoryException
+   *           If a problem occurs while parsing the definition
+   */
+  public static String parseNameFormOID(String definition) throws DirectoryException
+  {
+    return parseOID(definition, ERR_PARSING_NAME_FORM_OID);
+  }
+
+  /**
+   * Returns the OID from the provided DIT content rule definition, assuming the definition is valid.
+   * <p>
+   * This method does not perform any check.
+   *
+   * @param definition
+   *          The definition of a DIT content rule, assumed to be valid
+   * @return the OID, which is never {@code null}
+   * @throws DirectoryException
+   *           If a problem occurs while parsing the definition
+   */
+  public static String parseDITContentRuleOID(String definition) throws DirectoryException
+  {
+    return parseOID(definition, ERR_PARSING_DIT_CONTENT_RULE_OID);
+  }
+
+  /**
+   * Returns the ruleID from the provided DIT structure rule definition, assuming the definition is
+   * valid.
+   * <p>
+   * This method does not perform any check.
+   *
+   * @param definition
+   *          The definition of a DIT structure rule, assumed to be valid
+   * @return the OID, which is never {@code null}
+   * @throws DirectoryException
+   *           If a problem occurs while parsing the definition
+   */
+  public static int parseRuleID(String definition) throws DirectoryException
+  {
+    // Reuse code of parseOID, even though this is not an OID
+    return Integer.parseInt(parseOID(definition, ERR_PARSING_DIT_STRUCTURE_RULE_RULEID));
+  }
+
+  /**
+   * Returns the OID from the provided matching rule use definition, assuming the definition is valid.
+   * <p>
+   * This method does not perform any check.
+   *
+   * @param definition
+   *          The definition of a matching rule use, assumed to be valid
+   * @return the OID, which is never {@code null}
+   * @throws DirectoryException
+   *           If a problem occurs while parsing the definition
+   */
+  public static String parseMatchingRuleUseOID(String definition) throws DirectoryException
+  {
+    return parseOID(definition, ERR_PARSING_MATCHING_RULE_USE_OID);
+  }
+
+  /**
+   * Returns the OID from the provided syntax definition, assuming the definition is valid.
+   * <p>
+   * This method does not perform any check.
+   *
+   * @param definition
+   *          The definition of a syntax, assumed to be valid
+   * @return the OID, which is never {@code null}
+   * @throws DirectoryException
+   *           If a problem occurs while parsing the definition
+   */
+  public static String parseSyntaxOID(String definition) throws DirectoryException
+  {
+    return parseOID(definition, ERR_PARSING_LDAP_SYNTAX_OID);
+  }
+
+  /**
+   * Returns the OID from the provided definition, using the provided message if an error occurs.
+   *
+   * @param definition
+   *            The definition of a schema element
+   * @param parsingErrorMsg
+   *            Error message to use in case of failure (should be related to
+   *            the specific schema element parsed)
+   * @return the OID corresponding to the definition
+   * @throws DirectoryException
+   *            If the parsing of the definition fails
+   *
+   * */
+  public static String parseOID(String definition, Arg1<Object> parsingErrorMsg) throws DirectoryException
+  {
+    try
+    {
+      int pos = 0;
+      int length = definition.length();
+      // Skip over any leading whitespace.
+      while (pos < length && (definition.charAt(pos) == ' '))
+      {
+        pos++;
+      }
+      // Skip the open parenthesis.
+      pos++;
+      // Skip over any spaces immediately following the opening parenthesis.
+      while (pos < length && definition.charAt(pos) == ' ')
+      {
+        pos++;
+      }
+      // The next set of characters must be the OID.
+      int oidStartPos = pos;
+      while (pos < length && definition.charAt(pos) != ' ' && definition.charAt(pos) != ')')
+      {
+        pos++;
+      }
+      return definition.substring(oidStartPos, pos);
+    }
+    catch (IndexOutOfBoundsException e)
+    {
+      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, parsingErrorMsg.get(definition), e);
+    }
+  }
 }

--
Gitblit v1.10.0