From 3dc3f07607a5c720a1452cc75701554a9f3ad715 Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Mon, 24 Mar 2014 09:45:42 +0000
Subject: [PATCH] Checkpoint commit for OPENDJ-1308 Migrate schema support

---
 opendj3-server-dev/src/server/org/opends/server/api/SubstringMatchingRule.java |  260 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 260 insertions(+), 0 deletions(-)

diff --git a/opendj3-server-dev/src/server/org/opends/server/api/SubstringMatchingRule.java b/opendj3-server-dev/src/server/org/opends/server/api/SubstringMatchingRule.java
index 7984df1..b3d84fb 100644
--- a/opendj3-server-dev/src/server/org/opends/server/api/SubstringMatchingRule.java
+++ b/opendj3-server-dev/src/server/org/opends/server/api/SubstringMatchingRule.java
@@ -26,11 +26,18 @@
  */
 package org.opends.server.api;
 
+import java.util.Collection;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.TreeSet;
 
+import org.forgerock.opendj.ldap.Assertion;
 import org.forgerock.opendj.ldap.ByteSequence;
 import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ByteStringBuilder;
+import org.forgerock.opendj.ldap.ConditionResult;
 import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
 
 /**
  * This class defines the set of methods and structures that must be
@@ -175,5 +182,258 @@
 
     return true;
   }
+
+  /**
+   * Default assertion implementation for substring matching rules.
+   * For example, with the assertion value "initial*any1*any2*any3*final",
+   * the assertion will be decomposed like this:
+   * <ul>
+   * <li>normInitial will contain "initial"</li>
+   * <li>normAnys will contain [ "any1", "any2", "any3" ]</li>
+   * <li>normFinal will contain "final"</li>
+   * </ul>
+   */
+  static final class DefaultSubstringAssertion implements Assertion {
+      /** Normalized substring for the text before the first '*' character. */
+      private final ByteString normInitial;
+      /** Normalized substrings for all text chunks in between '*' characters. */
+      private final ByteString[] normAnys;
+      /** Normalized substring for the text after the last '*' character. */
+      private final ByteString normFinal;
+
+      private DefaultSubstringAssertion(final ByteString normInitial,
+              final ByteString[] normAnys, final ByteString normFinal) {
+          this.normInitial = normInitial;
+          this.normAnys = normAnys;
+          this.normFinal = normFinal;
+      }
+
+      /** {@inheritDoc} */
+      @Override
+      public ConditionResult matches(final ByteSequence normalizedAttributeValue) {
+          final int valueLength = normalizedAttributeValue.length();
+
+          int pos = 0;
+          if (normInitial != null) {
+              final int initialLength = normInitial.length();
+              if (initialLength > valueLength) {
+                  return ConditionResult.FALSE;
+              }
+
+              for (; pos < initialLength; pos++) {
+                  if (normInitial.byteAt(pos) != normalizedAttributeValue.byteAt(pos)) {
+                      return ConditionResult.FALSE;
+                  }
+              }
+          }
+
+          if (normAnys != null) {
+          matchEachSubstring:
+              for (final ByteSequence element : normAnys) {
+                  final int anyLength = element.length();
+                  final int end = valueLength - anyLength;
+              matchCurrentSubstring:
+                  for (; pos <= end; pos++) {
+                      // Try to match all characters from the substring
+                      for (int i = 0; i < anyLength; i++) {
+                          if (element.byteAt(i) != normalizedAttributeValue.byteAt(pos + i)) {
+                              // not a match,
+                              // try to find a match in the rest of this value
+                              continue matchCurrentSubstring;
+                          }
+                      }
+                      // we just matched current substring,
+                      // go try to match the next substring
+                      pos += anyLength;
+                      continue matchEachSubstring;
+                  }
+                  // Could not match current substring
+                  return ConditionResult.FALSE;
+              }
+          }
+
+          if (normFinal != null) {
+              final int finalLength = normFinal.length();
+
+              if (valueLength - finalLength < pos) {
+                  return ConditionResult.FALSE;
+              }
+
+              pos = valueLength - finalLength;
+              for (int i = 0; i < finalLength; i++, pos++) {
+                  if (normFinal.byteAt(i) != normalizedAttributeValue.byteAt(pos)) {
+                      return ConditionResult.FALSE;
+                  }
+              }
+          }
+
+          return ConditionResult.TRUE;
+      }
+
+      /** {@inheritDoc} */
+      @Override
+      public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException {
+          final Collection<T> subqueries = new LinkedList<T>();
+          if (normInitial != null) {
+              // relies on the fact that equality indexes are also ordered
+              subqueries.add(rangeMatch(factory, "equality", normInitial));
+          }
+          if (normAnys != null) {
+              for (ByteString normAny : normAnys) {
+                  substringMatch(factory, normAny, subqueries);
+              }
+          }
+          if (normFinal != null) {
+              substringMatch(factory, normFinal, subqueries);
+          }
+          if (normInitial != null) {
+              // Add this one last to minimize the risk to run the same search twice
+              // (possible overlapping with the use of equality index at the start of this method)
+              substringMatch(factory, normInitial, subqueries);
+          }
+          return factory.createIntersectionQuery(subqueries);
+      }
+
+      private <T> T rangeMatch(IndexQueryFactory<T> factory, String indexID, ByteSequence lower) {
+          // Iterate through all the keys that have this value as the prefix.
+
+          // Set the upper bound for a range search.
+          // We need a key for the upper bound that is of equal length
+          // but slightly greater than the lower bound.
+          final ByteStringBuilder upper = new ByteStringBuilder(lower);
+
+          for (int i = upper.length() - 1; i >= 0; i--) {
+              if (upper.byteAt(i) == (byte) 0xFF) {
+                  // We have to carry the overflow to the more significant byte.
+                  upper.setByte(i, (byte) 0);
+              } else {
+                  // No overflow, we can stop.
+                  upper.setByte(i, (byte) (upper.byteAt(i) + 1));
+                  break;
+              }
+          }
+
+          // Read the range: lower <= keys < upper.
+          return factory.createRangeMatchQuery(indexID, lower, upper, true, false);
+      }
+
+      private <T> void substringMatch(final IndexQueryFactory<T> factory, final ByteString normSubstring,
+              final Collection<T> subqueries) {
+          int substrLength = factory.getIndexingOptions().substringKeySize();
+
+          // There are two cases, depending on whether the user-provided
+          // substring is smaller than the configured index substring length or not.
+          if (normSubstring.length() < substrLength) {
+              subqueries.add(rangeMatch(factory, "substring", normSubstring));
+          } else {
+              // Break the value up into fragments of length equal to the
+              // index substring length, and read those keys.
+
+              // Eliminate duplicates by putting the keys into a set.
+              final TreeSet<ByteSequence> substringKeys = new TreeSet<ByteSequence>();
+
+              // Example: The value is ABCDE and the substring length is 3.
+              // We produce the keys ABC BCD CDE.
+              for (int first = 0, last = substrLength;
+                   last <= normSubstring.length(); first++, last++) {
+                  substringKeys.add(normSubstring.subSequence(first, first + substrLength));
+              }
+
+              for (ByteSequence key : substringKeys) {
+                  subqueries.add(factory.createExactMatchQuery("substring", key));
+              }
+          }
+      }
+
+      // TODO : reminder : should add this method in the SDK
+      /** {@inheritDoc} */
+      @Override
+    public int hashCode()
+    {
+      int hashCode = 0;
+      if (normInitial != null)
+      {
+        hashCode += normInitial.hashCode();
+      }
+      if (normAnys != null)
+      {
+        for (ByteString any : normAnys)
+        {
+          hashCode += any.hashCode();
+        }
+      }
+      if (normFinal != null)
+      {
+        hashCode += normFinal.hashCode();
+      }
+      return hashCode;
+    }
+
+      // TODO : reminder : should add this method in the SDK
+      /** {@inheritDoc} */
+      @Override
+      public boolean equals(Object obj)
+      {
+        if (obj == this)
+        {
+          return true;
+        }
+        if (! (obj instanceof DefaultSubstringAssertion))
+        {
+          return false;
+        }
+        DefaultSubstringAssertion other = (DefaultSubstringAssertion) obj;
+        boolean initialCheck = normInitial == null ? other.normInitial == null : normInitial.equals(other.normInitial);
+        if (!initialCheck)
+        {
+          return false;
+        }
+        boolean finalCheck = normFinal == null ? other.normFinal == null : normFinal.equals(other.normFinal);
+        if (!finalCheck)
+        {
+          return false;
+        }
+        boolean anyCheck = normAnys == null ? other.normAnys == null : normAnys.length == other.normAnys.length;
+        if (!anyCheck)
+        {
+          return false;
+        }
+        if (normAnys != null)
+        {
+          for (int i = 0; i < normAnys.length; i++)
+          {
+            if (! normAnys[i].equals(other.normAnys[i]))
+            {
+              return false;
+            }
+          }
+        }
+        return true;
+      }
+
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public Assertion getSubstringAssertion(ByteSequence subInitial,
+      List<? extends ByteSequence> subAnyElements, ByteSequence subFinal)
+      throws DecodeException
+  {
+    final ByteString normInitial = subInitial == null ? null : normalizeSubstring(subInitial);
+
+    ByteString[] normAnys = null;
+    if (subAnyElements != null && !subAnyElements.isEmpty())
+    {
+      normAnys = new ByteString[subAnyElements.size()];
+      for (int i = 0; i < subAnyElements.size(); i++)
+      {
+        normAnys[i] = normalizeSubstring(subAnyElements.get(i));
+      }
+    }
+    final ByteString normFinal = subFinal == null ? null : normalizeSubstring(subFinal);
+
+    return new DefaultSubstringAssertion(normInitial, normAnys, normFinal);
+  }
+
 }
 

--
Gitblit v1.10.0