From 9e6e6a27778d880d913a940738d18324854d1e70 Mon Sep 17 00:00:00 2001
From: Jean-Noel Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Fri, 07 Mar 2014 23:24:41 +0000
Subject: [PATCH] OPENDJ-1308 Migrate schema support

---
 opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/ApproximateMatchingRuleTest.java           |    2 
 opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/SubstringMatchingRuleTest.java             |   45 ++++--
 opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImplTest.java |  262 +++++++++++++++++++++++++++++++++++++++++++
 opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImpl.java     |   46 +++----
 4 files changed, 312 insertions(+), 43 deletions(-)

diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImpl.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImpl.java
index b731b7f..5d84db3 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImpl.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImpl.java
@@ -53,6 +53,9 @@
  */
 abstract class AbstractSubstringMatchingRuleImpl extends AbstractMatchingRuleImpl {
 
+    /** The backslash character. */
+    private static final int BACKSLASH = 0x5C;
+
     /**
      * Default assertion implementation for substring matching rules.
      * For example, with the assertion value "initial*any1*any2*any3*final",
@@ -97,36 +100,28 @@
                 }
             }
 
-            if (normAnys != null && normAnys.length != 0) {
+            if (normAnys != null) {
+            matchEachSubstring:
                 for (final ByteSequence element : normAnys) {
                     final int anyLength = element.length();
-                    if (anyLength == 0) {
-                        continue;
-                    }
                     final int end = valueLength - anyLength;
-                    boolean match = false;
+                matchCurrentSubstring:
                     for (; pos <= end; pos++) {
-                        if (element.byteAt(0) == normalizedAttributeValue.byteAt(pos)) {
-                            boolean subMatch = true;
-                            for (int i = 1; i < anyLength; i++) {
-                                if (element.byteAt(i) != normalizedAttributeValue.byteAt(pos + i)) {
-                                    subMatch = false;
-                                    break;
-                                }
-                            }
-
-                            if (subMatch) {
-                                match = subMatch;
-                                break;
+                        // 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;
                             }
                         }
-                    }
-
-                    if (match) {
+                        // we just matched current substring,
+                        // go try to match the next substring
                         pos += anyLength;
-                    } else {
-                        return ConditionResult.FALSE;
+                        continue matchEachSubstring;
                     }
+                    // Could not match current substring
+                    return ConditionResult.FALSE;
                 }
             }
 
@@ -181,7 +176,7 @@
             final ByteStringBuilder upper = new ByteStringBuilder(lower);
 
             for (int i = upper.length() - 1; i >= 0; i--) {
-                if (upper.byteAt(i) == 0xFF) {
+                if (upper.byteAt(i) == (byte) 0xFF) {
                     // We have to carry the overflow to the more significant byte.
                     upper.setByte(i, (byte) 0);
                 } else {
@@ -394,7 +389,7 @@
             b = (byte) 0xF0;
             break;
         default:
-            if (c1 == 0x5C) {
+            if (c1 == BACKSLASH) {
                 return c1;
             }
             if (escapeChars != null) {
@@ -483,6 +478,7 @@
 
     private ByteString evaluateEscapes(final SubstringReader reader, final char[] escapeChars,
             final boolean trim) throws DecodeException {
+        // FIXME JNR I believe the trim parameter is dead code
         return evaluateEscapes(reader, escapeChars, escapeChars, trim);
     }
 
@@ -500,7 +496,7 @@
         reader.mark();
         while (reader.remaining() > 0) {
             c = reader.read();
-            if (c == 0x5C /* The backslash character */) {
+            if (c == BACKSLASH) {
                 if (valueBuffer == null) {
                     valueBuffer = new ByteStringBuilder();
                 }
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImplTest.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImplTest.java
new file mode 100644
index 0000000..0a1f840
--- /dev/null
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImplTest.java
@@ -0,0 +1,262 @@
+/*
+ * 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 2014 ForgeRock AS
+ */
+package org.forgerock.opendj.ldap.schema;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.TreeSet;
+
+import org.fest.assertions.Assertions;
+import org.forgerock.opendj.ldap.*;
+import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
+import org.forgerock.opendj.ldap.spi.Indexer;
+import org.forgerock.opendj.ldap.spi.IndexingOptions;
+import org.forgerock.util.Utils;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.forgerock.opendj.ldap.ByteString.*;
+import static org.mockito.Mockito.*;
+import static org.testng.Assert.*;
+
+/**
+ * Tests all generic code of AbstractSubstringMatchingRuleImpl.
+ */
+@SuppressWarnings("javadoc")
+public class AbstractSubstringMatchingRuleImplTest extends SchemaTestCase {
+
+    private static class FakeSubstringMatchingRuleImpl extends AbstractSubstringMatchingRuleImpl {
+
+        /** {@inheritDoc} */
+        @Override
+        public ByteString normalizeAttributeValue(Schema schema, ByteSequence value) throws DecodeException {
+            return value.toByteString();
+        }
+
+    }
+
+    private static class FakeIndexQueryFactory implements IndexQueryFactory<String> {
+
+        private final IndexingOptions options;
+
+        public FakeIndexQueryFactory(IndexingOptions options) {
+            this.options = options;
+        }
+
+        @Override
+        public String createExactMatchQuery(String indexID, ByteSequence key) {
+            return "exactMatch(" + indexID + ", value=='" + key + "')";
+        }
+
+        @Override
+        public String createMatchAllQuery() {
+            return "matchAll()";
+        }
+
+        @Override
+        public String createRangeMatchQuery(String indexID, ByteSequence lower,
+                ByteSequence upper, boolean lowerIncluded, boolean upperIncluded) {
+            final StringBuilder sb = new StringBuilder("rangeMatch");
+            sb.append("(");
+            sb.append(indexID);
+            sb.append(", '");
+            sb.append(lower);
+            sb.append("' <");
+            if (lowerIncluded) {
+                sb.append("=");
+            }
+            sb.append(" value <");
+            if (upperIncluded) {
+                sb.append("=");
+            }
+            sb.append(" '");
+            sb.append(upper);
+            sb.append("')");
+            return sb.toString();
+        }
+
+        @Override
+        public String createIntersectionQuery(Collection<String> subqueries) {
+            return "intersect[" + Utils.joinAsString(", ", subqueries) + "]";
+        }
+
+        @Override
+        public String createUnionQuery(Collection<String> subqueries) {
+            return "union[" + Utils.joinAsString(", ", subqueries) + "]";
+        }
+
+        @Override
+        public IndexingOptions getIndexingOptions() {
+            return options;
+        }
+
+    }
+
+    private MatchingRuleImpl getRule() {
+        return new FakeSubstringMatchingRuleImpl();
+    }
+
+    private IndexingOptions newIndexingOptions() {
+        final IndexingOptions options = mock(IndexingOptions.class);
+        when(options.substringKeySize()).thenReturn(3);
+        return options;
+    }
+
+    @DataProvider
+    public Object[][] invalidAssertions() {
+        return new Object[][] {
+            { "" },
+            { "abc" },
+            { "**" },
+            { "\\g" },
+            { "\\0" },
+            { "\\0g" },
+        };
+    }
+
+    @Test(dataProvider = "invalidAssertions", expectedExceptions = { DecodeException.class })
+    public void testInvalidAssertion(String assertionValue) throws Exception {
+        getRule().getAssertion(null, valueOf(assertionValue));
+    }
+
+    @DataProvider
+    public Object[][] validAssertions() {
+        return new Object[][] {
+            { "this is a string", "*", ConditionResult.TRUE },
+            { "this is a string", "that*", ConditionResult.FALSE },
+            { "this is a string", "*that", ConditionResult.FALSE },
+            { "this is a string", "this*is*a*string", ConditionResult.TRUE },
+            { "this is a string", "this*my*string", ConditionResult.FALSE },
+            { "this is a string", "string*a*is*this", ConditionResult.FALSE },
+            // FIXME next line is not working (StringIndexOutOfBoundsException), is it incorrect?
+            // { "this is a string", "\\00", ConditionResult.FALSE },
+            // FIXME next line is not working (DecodeException), is it incorrect?
+            // { "this is a string", gen(), ConditionResult.FALSE },
+            // initial longer than value
+            { "tt", "this*", ConditionResult.FALSE },
+            { "tt", "*this", ConditionResult.FALSE },
+        };
+    }
+
+    private String gen() {
+        final char[] array = new char[] {
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+        final StringBuilder sb = new StringBuilder();
+        for (char c : array) {
+            sb.append("\\").append(c).append(c);
+        }
+        return sb.toString();
+    }
+
+    @Test(dataProvider = "validAssertions")
+    public void testValidAssertions(String attrValue, String assertionValue, ConditionResult expected)
+            throws Exception {
+        final MatchingRuleImpl rule = getRule();
+        final ByteString normValue = rule.normalizeAttributeValue(null, valueOf(attrValue));
+        Assertion assertion = rule.getAssertion(null, valueOf(assertionValue));
+        assertEquals(assertion.matches(normValue), expected);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testSubstringCreateIndexQueryForFinalWithMultipleSubqueries() throws Exception {
+        Assertion assertion = getRule().getSubstringAssertion(
+            null, null, Collections.EMPTY_LIST, valueOf("this"));
+
+        assertEquals(
+            assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions())),
+            "intersect["
+                    + "exactMatch(substring, value=='his'), "
+                    + "exactMatch(substring, value=='thi')"
+                    + "]");
+    }
+
+    @Test
+    public void testSubstringCreateIndexQueryForAllNoSubqueries() throws Exception {
+        Assertion assertion = getRule().getSubstringAssertion(
+            null, valueOf("abc"), Arrays.asList(toByteStrings("def", "ghi")), valueOf("jkl"));
+
+        assertEquals(
+            assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions())),
+            "intersect["
+                    + "rangeMatch(equality, 'abc' <= value < 'abd'), "
+                    + "exactMatch(substring, value=='def'), "
+                    + "exactMatch(substring, value=='ghi'), "
+                    + "exactMatch(substring, value=='jkl'), "
+                    + "exactMatch(substring, value=='abc')"
+                    + "]");
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testSubstringCreateIndexQueryWithInitial() throws Exception {
+        Assertion assertion = getRule().getSubstringAssertion(
+            null, valueOf("aa"), Collections.EMPTY_LIST, null);
+
+        assertEquals(
+            assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions())),
+            "intersect["
+                    + "rangeMatch(equality, 'aa' <= value < 'ab'), "
+                    + "rangeMatch(substring, 'aa' <= value < 'ab')"
+                    + "]");
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testSubstringCreateIndexQueryWithInitialOverflowsInRange() throws Exception {
+        ByteString lower = wrap(new byte[] { 'a', (byte) 0XFF });
+        Assertion assertion = getRule().getSubstringAssertion(
+            null, lower, Collections.EMPTY_LIST, null);
+
+        assertEquals(
+            assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions())),
+            // 0x00 is the nul byte, a.k.a. string terminator
+            // so everything after it is not part of the string
+            "intersect["
+                    + "rangeMatch(equality, '" + lower + "' <= value < 'b\u0000'), "
+                    + "rangeMatch(substring, '" + lower + "' <= value < 'b\u0000')"
+                    + "]");
+    }
+
+    @Test
+    public void testIndexer() throws Exception {
+        final Indexer indexer = getRule().getIndexers().iterator().next();
+        Assertions.assertThat(indexer.getIndexID()).isEqualTo("substring");
+
+        final IndexingOptions options = newIndexingOptions();
+        final TreeSet<ByteString> keys = new TreeSet<ByteString>();
+        indexer.createKeys(Schema.getCoreSchema(), valueOf("ABCDE"), options, keys);
+        Assertions.assertThat(keys).containsOnly((Object[]) toByteStrings("ABC", "BCD", "CDE", "DE", "E"));
+    }
+
+    private ByteString[] toByteStrings(String... strings) {
+        final ByteString[] results = new ByteString[strings.length];
+        for (int i = 0; i < strings.length; i++) {
+            results[i] = valueOf(strings[i]);
+        }
+        return results;
+    }
+}
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/ApproximateMatchingRuleTest.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/ApproximateMatchingRuleTest.java
index dd52f82..1dbb0a3 100644
--- a/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/ApproximateMatchingRuleTest.java
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/ApproximateMatchingRuleTest.java
@@ -62,7 +62,7 @@
     @DataProvider(name = "approximatematchingrules")
     public Object[][] createapproximateMatchingRuleTest() {
         // fill this table with tables containing :
-        // - the name of the approxiamtematchingrule to test
+        // - the name of the approximate matching rule to test
         // - 2 values that must be tested for matching
         // - a boolean indicating if the values match or not
         return new Object[][] { { metaphone, "celebre", "selebre", ConditionResult.TRUE },
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/SubstringMatchingRuleTest.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/SubstringMatchingRuleTest.java
index 28bdc0e..62b8e91 100644
--- a/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/SubstringMatchingRuleTest.java
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/SubstringMatchingRuleTest.java
@@ -25,7 +25,7 @@
  */
 package org.forgerock.opendj.ldap.schema;
 
-import static org.testng.Assert.fail;
+import static org.testng.Assert.*;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -94,11 +94,13 @@
         // normalize the 2 provided values and check that they are equals
         final ByteString normalizedValue = rule.normalizeAttributeValue(ByteString.valueOf(value));
 
-        if (rule.getSubstringAssertion(null, null, ByteString.valueOf(finalValue)).matches(normalizedValue) != result
-                || rule.getAssertion(ByteString.valueOf("*" + finalValue)).matches(normalizedValue) != result) {
-            fail("final substring matching rule " + rule + " does not give expected result ("
-                    + result + ") for values : " + value + " and " + finalValue);
-        }
+        final ConditionResult substringAssertionMatches =
+            rule.getSubstringAssertion(null, null, ByteString.valueOf(finalValue)).matches(normalizedValue);
+        final ConditionResult assertionMatches =
+            rule.getAssertion(ByteString.valueOf("*" + finalValue)).matches(normalizedValue);
+        final String message = getMessage("final", rule, value, finalValue);
+        assertEquals(substringAssertionMatches, result, message);
+        assertEquals(assertionMatches, result, message);
     }
 
     /**
@@ -112,11 +114,19 @@
         // normalize the 2 provided values and check that they are equals
         final ByteString normalizedValue = rule.normalizeAttributeValue(ByteString.valueOf(value));
 
-        if (rule.getSubstringAssertion(ByteString.valueOf(initial), null, null).matches(normalizedValue) != result
-                || rule.getAssertion(ByteString.valueOf(initial + "*")).matches(normalizedValue) != result) {
-            fail("initial substring matching rule " + rule + " does not give expected result ("
-                    + result + ") for values : " + value + " and " + initial);
-        }
+        final ConditionResult substringAssertionMatches =
+            rule.getSubstringAssertion(ByteString.valueOf(initial), null, null).matches(normalizedValue);
+        final ConditionResult assertionMatches =
+            rule.getAssertion(ByteString.valueOf(initial + "*")).matches(normalizedValue);
+        final String message = getMessage("initial", rule, value, initial);
+        assertEquals(substringAssertionMatches, result, message);
+        assertEquals(assertionMatches, result, message);
+    }
+
+    private String getMessage(final String prefix, final MatchingRule rule,
+            final String value, final String assertionValue) {
+        return prefix + " substring matching rule " + rule
+                + " failed for values : \"" + value + "\" and \"" + assertionValue + "\".";
     }
 
     /**
@@ -182,12 +192,13 @@
             middleList.add(ByteString.valueOf(middleSub));
         }
 
-        if (rule.getSubstringAssertion(null, middleList, null).matches(normalizedValue) != result
-                || rule.getAssertion(ByteString.valueOf(printableMiddleSubs)).matches(
-                        normalizedValue) != result) {
-            fail("middle substring matching rule " + rule + " does not give expected result ("
-                    + result + ") for values : " + value + " and " + printableMiddleSubs);
-        }
+        final ConditionResult substringAssertionMatches =
+            rule.getSubstringAssertion(null, middleList, null).matches(normalizedValue);
+        final ConditionResult assertionMatches =
+            rule.getAssertion(ByteString.valueOf(printableMiddleSubs)).matches(normalizedValue);
+        final String message = getMessage("middle", rule, value, printableMiddleSubs.toString());
+        assertEquals(substringAssertionMatches, result, message);
+        assertEquals(assertionMatches, result, message);
     }
 
     /**

--
Gitblit v1.10.0