mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Nicolas Capponi
07.23.2014 7d5626f01a5ff72a8b9bbd803f6a3f4a0926a5da
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractEqualityMatchingRuleImpl.java
@@ -39,8 +39,7 @@
 */
abstract class AbstractEqualityMatchingRuleImpl extends AbstractMatchingRuleImpl {
    private final Collection<? extends Indexer> indexers =
            Collections.singleton(new DefaultIndexer("equality"));
    private final Collection<? extends Indexer> indexers = Collections.singleton(new DefaultIndexer("equality"));
    AbstractEqualityMatchingRuleImpl() {
        // Nothing to do.
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractMatchingRuleImpl.java
@@ -51,11 +51,15 @@
        private final ByteSequence normalizedAssertionValue;
        static DefaultAssertion equality(final ByteSequence normalizedAssertionValue) {
            return new DefaultAssertion("equality", normalizedAssertionValue);
            return named("equality", normalizedAssertionValue);
        }
        static DefaultAssertion approximate(final ByteSequence normalizedAssertionValue) {
            return new DefaultAssertion("approximate", normalizedAssertionValue);
            return named("approximate", normalizedAssertionValue);
        }
        static DefaultAssertion named(final String indexID, final ByteSequence normalizedAssertionValue) {
            return new DefaultAssertion(indexID, normalizedAssertionValue);
        }
        private DefaultAssertion(final String indexID, final ByteSequence normalizedAssertionValue) {
@@ -92,7 +96,7 @@
        public String getIndexID() {
            return indexID;
        }
    };
    }
    private static final Assertion UNDEFINED_ASSERTION = new Assertion() {
        @Override
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractOrderingMatchingRuleImpl.java
@@ -47,11 +47,19 @@
 */
abstract class AbstractOrderingMatchingRuleImpl extends AbstractMatchingRuleImpl {
    private final Collection<? extends Indexer> indexers =
            Collections.singleton(new DefaultIndexer("ordering"));
    private final Collection<? extends Indexer> indexers;
    private final String indexId;
    /** Constructor for default matching rules. */
    AbstractOrderingMatchingRuleImpl() {
        // Nothing to do.
        this("ordering");
    }
    /** Constructor for non-default matching rules. */
    AbstractOrderingMatchingRuleImpl(String indexId) {
        this.indexId = indexId;
        this.indexers = Collections.singleton(new DefaultIndexer(indexId));
    }
    /** {@inheritDoc} */
@@ -67,15 +75,14 @@
            @Override
            public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException {
                return factory.createRangeMatchQuery("ordering", ByteString.empty(), normAssertion, false, false);
                return factory.createRangeMatchQuery(indexId, ByteString.empty(), normAssertion, false, false);
            }
        };
    }
    /** {@inheritDoc} */
    @Override
    public Assertion getGreaterOrEqualAssertion(final Schema schema, final ByteSequence value)
            throws DecodeException {
    public Assertion getGreaterOrEqualAssertion(final Schema schema, final ByteSequence value) throws DecodeException {
        final ByteString normAssertion = normalizeAttributeValue(schema, value);
        return new Assertion() {
            @Override
@@ -85,7 +92,36 @@
            @Override
            public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException {
                return factory.createRangeMatchQuery("ordering", normAssertion, ByteString.empty(), true, false);
                return factory.createRangeMatchQuery(indexId, normAssertion, ByteString.empty(), true, false);
            }
        };
    }
    /**
     * Retrieves the normalized form of the provided assertion value, which is
     * best suited for efficiently performing greater than matching
     * operations on that value. The assertion value is guaranteed to be valid
     * against this matching rule's assertion syntax.
     *
     * @param schema
     *            The schema in which this matching rule is defined.
     * @param assertionValue
     *            The syntax checked assertion value to be normalized.
     * @return The normalized version of the provided assertion value.
     * @throws DecodeException
     *             if an syntax error occurred while parsing the value.
     */
    public Assertion getGreaterThanAssertion(Schema schema, ByteSequence assertionValue) throws DecodeException {
        final ByteString normAssertion = normalizeAttributeValue(schema, assertionValue);
        return new Assertion() {
            @Override
            public ConditionResult matches(final ByteSequence attributeValue) {
                return ConditionResult.valueOf(attributeValue.compareTo(normAssertion) > 0);
            }
            @Override
            public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException {
                return factory.createRangeMatchQuery(indexId, normAssertion, ByteString.empty(), false, false);
            }
        };
    }
@@ -103,7 +139,7 @@
            @Override
            public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException {
                return factory.createRangeMatchQuery("ordering", ByteString.empty(), normAssertion, false, true);
                return factory.createRangeMatchQuery(indexId, ByteString.empty(), normAssertion, false, true);
            }
        };
    }
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImpl.java
@@ -34,7 +34,6 @@
import java.util.List;
import java.util.TreeSet;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.ldap.Assertion;
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.ByteString;
@@ -66,7 +65,7 @@
     * <li>normFinal will contain "final"</li>
     * </ul>
     */
    static final class DefaultSubstringAssertion implements Assertion {
    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. */
@@ -74,8 +73,9 @@
        /** Normalized substring for the text after the last '*' character. */
        private final ByteString normFinal;
        private DefaultSubstringAssertion(final ByteString normInitial,
                final ByteString[] normAnys, final ByteString normFinal) {
        private DefaultSubstringAssertion(final ByteString normInitial, final ByteString[] normAnys,
                final ByteString normFinal) {
            this.normInitial = normInitial;
            this.normAnys = normAnys;
            this.normFinal = normFinal;
@@ -149,7 +149,7 @@
            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));
                subqueries.add(rangeMatch(factory, equalityIndexId, normInitial));
            }
            if (normAnys != null) {
                for (ByteString normAny : normAnys) {
@@ -164,6 +164,11 @@
                // (possible overlapping with the use of equality index at the start of this method)
                substringMatch(factory, normInitial, subqueries);
            }
            if (normInitial == null && (normAnys == null || normAnys.length == 0) && normFinal == null) {
                // Can happen with a filter like "cn:en.6:=*", just return an empty record
                return factory.createMatchAllQuery();
            }
            return factory.createIntersectionQuery(subqueries);
        }
@@ -197,7 +202,7 @@
            // 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));
                subqueries.add(rangeMatch(factory, substringIndexId, normSubstring));
            } else {
                // Break the value up into fragments of length equal to the
                // index substring length, and read those keys.
@@ -213,7 +218,7 @@
                }
                for (ByteSequence key : substringKeys) {
                    subqueries.add(factory.createExactMatchQuery("substring", key));
                    subqueries.add(factory.createExactMatchQuery(substringIndexId, key));
                }
            }
        }
@@ -243,15 +248,27 @@
        /** {@inheritDoc} */
        @Override
        public String getIndexID() {
            return "substring";
            return substringIndexId;
        }
    }
    private final Collection<? extends Indexer> indexers =
              Collections.singleton(new SubstringIndexer());
    private final Collection<? extends Indexer> indexers = Collections.singleton(new SubstringIndexer());
    /** Identifier of the substring index. */
    private final String substringIndexId;
    /** Identifier of the equality index. */
    private final String equalityIndexId;
    /** Constructor for default matching rules. */
    AbstractSubstringMatchingRuleImpl() {
        // Nothing to do.
        this("substring", "equality");
    }
    /** Constructor for non-default matching rules. */
    AbstractSubstringMatchingRuleImpl(String substringIndexId, String equalityIndexId) {
        this.substringIndexId = substringIndexId;
        this.equalityIndexId = equalityIndexId;
    }
    /** {@inheritDoc} */
@@ -276,7 +293,7 @@
        ByteString bytes = evaluateEscapes(reader, escapeChars, false);
        if (bytes.length() > 0) {
            initialString = normalizeSubString(schema, bytes);
            initialString = bytes;
        }
        if (reader.remaining() == 0) {
            throw DecodeException.error(WARN_ATTR_SYNTAX_SUBSTRING_NO_WILDCARDS.get(assertionValue));
@@ -292,10 +309,10 @@
                if (anyStrings == null) {
                    anyStrings = new LinkedList<ByteSequence>();
                }
                anyStrings.add(normalizeSubString(schema, bytes));
                anyStrings.add(bytes);
            } else {
                if (bytes.length() > 0) {
                    finalString = normalizeSubString(schema, bytes);
                    finalString = bytes;
                }
                break;
            }
@@ -399,17 +416,13 @@
                    }
                }
            }
            final LocalizableMessage message = ERR_INVALID_ESCAPE_CHAR.get(reader.getString(), c1);
            throw DecodeException.error(message);
            throw DecodeException.error(ERR_INVALID_ESCAPE_CHAR.get(reader.getString(), c1));
        }
        // The two positions must be the hex characters that
        // comprise the escaped value.
        if (reader.remaining() == 0) {
            final LocalizableMessage message =
                    ERR_HEX_DECODE_INVALID_LENGTH.get(reader.getString());
            throw DecodeException.error(message);
            throw DecodeException.error(ERR_HEX_DECODE_INVALID_LENGTH.get(reader.getString()));
        }
        final char c2 = reader.read();
@@ -469,9 +482,7 @@
            b |= 0x0F;
            break;
        default:
            final LocalizableMessage message =
                    ERR_HEX_DECODE_INVALID_CHARACTER.get(new String(new char[] { c1, c2 }), c1);
            throw DecodeException.error(message);
            throw DecodeException.error(ERR_HEX_DECODE_INVALID_CHARACTER.get(new String(new char[] { c1, c2 }), c1));
        }
        return (char) b;
    }
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CollationMatchingRulesImpl.java
New file
@@ -0,0 +1,365 @@
/*
 * 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.text.Collator;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.opendj.ldap.Assertion;
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.spi.Indexer;
/**
 * Implementations of collation matching rules. Each matching rule is created
 * from a locale (eg, "en-US" or "en-GB").
 * <p>
 * The PRIMARY strength is used for collation, which means that only primary
 * differences are considered significant for comparison. It usually means that
 * spaces, case, and accent are not significant, although it is language dependant.
 * <p>
 * For a given locale, two indexes are used: a shared one (for equality and
 * ordering rules) and a substring one (for substring rule).
 */
public final class CollationMatchingRulesImpl {
    private static final String INDEX_ID_SHARED = "shared";
    private static final String INDEX_ID_SUBSTRING = "substring";
    private CollationMatchingRulesImpl() {
        // This class is not instanciable
    }
    /**
     * Creates a collation equality matching Rule.
     *
     * @param locale
     *            the locale to use for this rule
     * @return the matching rule implementation
     */
    public static CollationEqualityMatchingRuleImpl equalityMatchingRule(Locale locale) {
        return new CollationEqualityMatchingRuleImpl(locale);
    }
    /**
     * Creates a collation substring matching Rule.
     *
     * @param locale
     *            the locale to use for this rule
     * @return the matching rule implementation
     */
    public static CollationSubstringMatchingRuleImpl substringMatchingRule(Locale locale) {
        return new CollationSubstringMatchingRuleImpl(locale);
    }
    /**
     * Creates a collation less than matching Rule.
     *
     * @param locale
     *            the locale to use for this rule
     * @return the matching rule implementation
     */
    public static CollationLessThanMatchingRuleImpl lessThanMatchingRule(Locale locale) {
        return new CollationLessThanMatchingRuleImpl(locale);
    }
    /**
     * Creates a collation less than or equal matching Rule.
     *
     * @param locale
     *            the locale to use for this rule
     * @return the matching rule implementation
     */
    public static CollationLessThanOrEqualToMatchingRuleImpl lessThanOrEqualMatchingRule(Locale locale) {
        return new CollationLessThanOrEqualToMatchingRuleImpl(locale);
    }
    /**
     * Creates a collation greater than matching Rule.
     *
     * @param locale
     *            the locale to use for this rule
     * @return the matching rule implementation
     */
    public static CollationGreaterThanMatchingRuleImpl greaterThanMatchingRule(Locale locale) {
        return new CollationGreaterThanMatchingRuleImpl(locale);
    }
    /**
     * Creates a collation greater than or equal matching Rule.
     *
     * @param locale
     *            the locale to use for this rule
     * @return the matching rule implementation
     */
    public static CollationGreaterThanOrEqualToMatchingRuleImpl greaterThanOrEqualToMatchingRule(Locale locale) {
        return new CollationGreaterThanOrEqualToMatchingRuleImpl(locale);
    }
    /**
     * Defines the base for collation matching rules.
     */
    static abstract class AbstractCollationMatchingRuleImpl extends AbstractMatchingRuleImpl {
        private final Locale locale;
        final Collator collator;
        final Indexer indexer;
        /**
         * Creates the collation matching rule with the provided locale.
         *
         * @param locale
         *            Locale associated with this rule.
         */
        public AbstractCollationMatchingRuleImpl(Locale locale) {
            this.locale = locale;
            this.collator = createCollator(locale);
            this.indexer = new DefaultIndexer(getSharedIndexName());
        }
        private Collator createCollator(Locale locale) {
            Collator collator = Collator.getInstance(locale);
            collator.setStrength(Collator.PRIMARY);
            collator.setDecomposition(Collator.FULL_DECOMPOSITION);
            return collator;
        }
        /**
         * Returns the prefix name of the index database for this matching rule. An
         * index name for this rule will be based upon the Locale. This will
         * ensure that multiple collation matching rules corresponding to the
         * same Locale can share the same index database.
         *
         * @return The prefix name of the index for this matching rule.
         */
        String getPrefixIndexName() {
            String language = locale.getLanguage();
            String country = locale.getCountry();
            String variant = locale.getVariant();
            StringBuilder builder = new StringBuilder(language);
            if (country != null && country.length() > 0) {
                builder.append("_");
                builder.append(country);
            }
            if (variant != null && variant.length() > 0) {
                builder.append("_");
                builder.append(variant);
            }
            return builder.toString();
        }
        String getSharedIndexName() {
            return getPrefixIndexName() + "." + INDEX_ID_SHARED;
        }
        /** {@inheritDoc} */
        @Override
        public Collection<? extends Indexer> getIndexers() {
            return Collections.singletonList(indexer);
        }
        /** {@inheritDoc} */
        @Override
        public ByteString normalizeAttributeValue(final Schema schema, final ByteSequence value)
                throws DecodeException {
            try {
                final byte[] byteArray = collator.getCollationKey(value.toString()).toByteArray();
                // Last 4 bytes are 0s when collator strength is set to PRIMARY, so skip them
                return ByteString.wrap(byteArray).subSequence(0, byteArray.length - 4);
            } catch (final LocalizedIllegalArgumentException e) {
                throw DecodeException.error(e.getMessageObject());
            }
        }
    }
    /**
     * Defines the collation equality matching rule.
     */
    static final class CollationEqualityMatchingRuleImpl extends AbstractCollationMatchingRuleImpl {
        /**
         * Creates the matching rule with the provided locale.
         *
         * @param locale
         *          Locale associated with this rule.
         */
        CollationEqualityMatchingRuleImpl(Locale locale) {
            super(locale);
        }
        @Override
        public Assertion getAssertion(final Schema schema, final ByteSequence assertionValue)
                throws DecodeException {
            return DefaultAssertion.named(getSharedIndexName(), normalizeAttributeValue(schema, assertionValue));
        }
    }
    /**
     * Defines the collation substring matching rule.
     */
    static final class CollationSubstringMatchingRuleImpl extends AbstractCollationMatchingRuleImpl {
        private final AbstractSubstringMatchingRuleImpl substringMatchingRule;
        private final Indexer subIndexer;
        /**
         * Creates the matching rule with the provided locale.
         *
         * @param locale
         *          Locale associated with this rule.
         */
        CollationSubstringMatchingRuleImpl(Locale locale) {
            super(locale);
            substringMatchingRule = new AbstractSubstringMatchingRuleImpl(
                getPrefixIndexName() + "." + INDEX_ID_SUBSTRING, getSharedIndexName()) {
                @Override
                public ByteString normalizeAttributeValue(Schema schema, ByteSequence value)
                        throws DecodeException {
                    return CollationSubstringMatchingRuleImpl.this.normalizeAttributeValue(schema, value);
                }
            };
            // default substring matching rule has exactly one indexer
            subIndexer = substringMatchingRule.getIndexers().iterator().next();
        }
        @Override
        public Assertion getAssertion(final Schema schema, final ByteSequence assertionValue) throws DecodeException {
            return substringMatchingRule.getAssertion(schema, assertionValue);
        }
        /** {@inheritDoc} */
        @Override
        public Assertion getSubstringAssertion(Schema schema, ByteSequence subInitial,
                List<? extends ByteSequence> subAnyElements, ByteSequence subFinal) throws DecodeException {
            return substringMatchingRule.getSubstringAssertion(schema, subInitial, subAnyElements, subFinal);
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public final Collection<? extends Indexer> getIndexers() {
            return Arrays.asList(subIndexer, indexer);
        }
    }
    /**
     * Defines the collation ordering matching rule.
     */
    static abstract class CollationOrderingMatchingRuleImpl extends AbstractCollationMatchingRuleImpl {
        final AbstractOrderingMatchingRuleImpl orderingMatchingRule;
        /**
         * Creates the matching rule with the provided locale.
         *
         * @param locale
         *          Locale associated with this rule.
         */
        CollationOrderingMatchingRuleImpl(Locale locale) {
            super(locale);
            orderingMatchingRule = new AbstractOrderingMatchingRuleImpl(getSharedIndexName()) {
                @Override
                public ByteString normalizeAttributeValue(Schema schema, ByteSequence value) throws DecodeException {
                    return CollationOrderingMatchingRuleImpl.this.normalizeAttributeValue(schema, value);
                }
            };
        }
    }
    /**
     * Defines the collation less than matching rule.
     */
    static final class CollationLessThanMatchingRuleImpl extends CollationOrderingMatchingRuleImpl {
        CollationLessThanMatchingRuleImpl(Locale locale) {
            super(locale);
        }
        /** {@inheritDoc} */
        @Override
        public Assertion getAssertion(Schema schema, ByteSequence assertionValue) throws DecodeException {
            return orderingMatchingRule.getAssertion(schema, assertionValue);
        }
    }
    /**
     * Defines the collation less than or equal matching rule.
     */
    static final class CollationLessThanOrEqualToMatchingRuleImpl extends CollationOrderingMatchingRuleImpl {
        CollationLessThanOrEqualToMatchingRuleImpl(Locale locale) {
            super(locale);
        }
        /** {@inheritDoc} */
        @Override
        public Assertion getAssertion(Schema schema, ByteSequence assertionValue) throws DecodeException {
            return orderingMatchingRule.getLessOrEqualAssertion(schema, assertionValue);
        }
    }
    /**
     * Defines the collation greater than matching rule.
     */
    static final class CollationGreaterThanMatchingRuleImpl extends CollationOrderingMatchingRuleImpl {
        CollationGreaterThanMatchingRuleImpl(Locale locale) {
            super(locale);
        }
        /** {@inheritDoc} */
        @Override
        public Assertion getAssertion(Schema schema, ByteSequence assertionValue) throws DecodeException {
            return orderingMatchingRule.getGreaterThanAssertion(schema, assertionValue);
        }
    }
    /**
     * Defines the collation greater than or equal matching rule.
     */
    static final class CollationGreaterThanOrEqualToMatchingRuleImpl extends CollationOrderingMatchingRuleImpl {
        CollationGreaterThanOrEqualToMatchingRuleImpl(Locale locale) {
            super(locale);
        }
        /** {@inheritDoc} */
        @Override
        public Assertion getAssertion(Schema schema, ByteSequence assertionValue) throws DecodeException {
            return orderingMatchingRule.getGreaterOrEqualAssertion(schema, assertionValue);
        }
    }
}
opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImplTest.java
@@ -62,17 +62,24 @@
    }
    private static class FakeIndexQueryFactory implements IndexQueryFactory<String> {
    static class FakeIndexQueryFactory implements IndexQueryFactory<String> {
        private final IndexingOptions options;
        private final boolean normalizedValuesAreReadable;
        public FakeIndexQueryFactory(IndexingOptions options) {
            this(options, true);
        }
        public FakeIndexQueryFactory(IndexingOptions options, boolean normalizedValuesAreReadable) {
            this.options = options;
            this.normalizedValuesAreReadable = normalizedValuesAreReadable;
        }
        @Override
        public String createExactMatchQuery(String indexID, ByteSequence key) {
            return "exactMatch(" + indexID + ", value=='" + key + "')";
            String keyValue = normalizedValuesAreReadable ? key.toString() : key.toByteString().toHexString();
            return "exactMatch(" + indexID + ", value=='" + keyValue + "')";
        }
        @Override
@@ -81,13 +88,17 @@
        }
        @Override
        public String createRangeMatchQuery(String indexID, ByteSequence lower,
                ByteSequence upper, boolean lowerIncluded, boolean upperIncluded) {
        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);
            if (normalizedValuesAreReadable) {
                sb.append(lower);
            } else if (!lower.isEmpty()) {
                sb.append(lower.toByteString().toHexString());
            }
            sb.append("' <");
            if (lowerIncluded) {
                sb.append("=");
@@ -97,7 +108,11 @@
                sb.append("=");
            }
            sb.append(" '");
            sb.append(upper);
            if (normalizedValuesAreReadable) {
                sb.append(upper);
            } else if (!upper.isEmpty()) {
                sb.append(upper.toByteString().toHexString());
            }
            sb.append("')");
            return sb.toString();
        }
@@ -123,7 +138,7 @@
        return new FakeSubstringMatchingRuleImpl();
    }
    private IndexingOptions newIndexingOptions() {
    static IndexingOptions newIndexingOptions() {
        final IndexingOptions options = mock(IndexingOptions.class);
        when(options.substringKeySize()).thenReturn(3);
        return options;
opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationEqualityMatchingRuleTest.java
New file
@@ -0,0 +1,120 @@
/*
 * 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.Locale;
import org.forgerock.opendj.ldap.Assertion;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ConditionResult;
import org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.FakeIndexQueryFactory;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.*;
import static org.testng.Assert.*;
@SuppressWarnings("javadoc")
@Test
public class CollationEqualityMatchingRuleTest extends MatchingRuleTest {
    /**
     * {@inheritDoc}
     */
    @Override
    @DataProvider(name = "matchingRuleInvalidAttributeValues")
    public Object[][] createMatchingRuleInvalidAttributeValues() {
        return new Object[][] { };
    }
    /**
     * {@inheritDoc}
     */
    @Override
    @DataProvider(name = "matchingrules")
    public Object[][] createMatchingRuleTest() {
        return new Object[][] {
            // \u00E9 = LATIN SMALL LETTER E WITH ACUTE
            // \u00C9 = LATIN CAPITAL LETTER E WITH ACUTE
            { "passe", "passe", ConditionResult.TRUE },
            { "passe ", "passe", ConditionResult.TRUE },
            { "passE", "passe", ConditionResult.TRUE },
            { "pass\u00E9", "passe", ConditionResult.TRUE },
            { "pass\u00E9", "passE", ConditionResult.TRUE },
            { "pass\u00E9", "pass\u00C9", ConditionResult.TRUE },
            { "passe", "pass\u00E9", ConditionResult.TRUE },
            { "passE", "pass\u00E9", ConditionResult.TRUE },
            { "pass\u00C9", "pass\u00E9", ConditionResult.TRUE },
            { "abc", "abd", ConditionResult.FALSE },
            { "abc", "acc", ConditionResult.FALSE },
            { "abc", "bbc", ConditionResult.FALSE },
            { "abc", "abD", ConditionResult.FALSE },
            { "def", "d\u00E9g", ConditionResult.FALSE },
            { "def", "dEg", ConditionResult.FALSE },
            { "dEf", "deg", ConditionResult.FALSE },
            { "d\u00E9f", "dEg", ConditionResult.FALSE },
            { "abd", "abc", ConditionResult.FALSE },
            { "acc", "abc", ConditionResult.FALSE },
            { "bbc", "abc", ConditionResult.FALSE },
            { "abD", "abc", ConditionResult.FALSE },
            { "d\u00E9g", "def", ConditionResult.FALSE },
            { "dEg", "def", ConditionResult.FALSE },
            { "deg", "dEf", ConditionResult.FALSE },
            { "dEg", "d\u00E9f", ConditionResult.FALSE },
        };
    }
    /**
     * {@inheritDoc}
     */
    @Override
    protected MatchingRule getRule() {
        // Note that oid and names are not used by the test (ie, they could be any value, test should pass anyway)
        // Only the implementation class and the provided locale are really tested here.
        String oid = "1.3.6.1.4.1.42.2.27.9.4.76.1.3";
        Schema schema = new SchemaBuilder(Schema.getCoreSchema()).
            buildMatchingRule(oid).
                syntaxOID(SchemaConstants.SYNTAX_DIRECTORY_STRING_OID).
                names("fr.eq").
                implementation(CollationMatchingRulesImpl.equalityMatchingRule(new Locale("fr"))).
                addToSchema().
            toSchema();
        return schema.getMatchingRule(oid);
    }
    @Test
    public void testCreateIndexQuery() throws Exception {
        ByteString value = ByteString.valueOf("abc");
        MatchingRule matchingRule = getRule();
        Assertion assertion = matchingRule.getAssertion(value);
        String indexQuery = assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions(), false));
        ByteString normalizedValue = matchingRule.normalizeAttributeValue(value);
        assertEquals(indexQuery, "exactMatch(fr.shared, value=='" + normalizedValue.toHexString() + "')");
    }
}
opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationGreaterThanMatchingRuleTest.java
New file
@@ -0,0 +1,119 @@
/*
 * 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.Locale;
import org.forgerock.opendj.ldap.Assertion;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ConditionResult;
import org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.FakeIndexQueryFactory;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.*;
import static org.testng.Assert.*;
@SuppressWarnings("javadoc")
@Test
public class CollationGreaterThanMatchingRuleTest extends MatchingRuleTest {
    /**
     * {@inheritDoc}
     */
    @Override
    @DataProvider(name = "matchingRuleInvalidAttributeValues")
    public Object[][] createMatchingRuleInvalidAttributeValues() {
        return new Object[][] { };
    }
    /**
     * {@inheritDoc}
     */
    @Override
    @DataProvider(name = "matchingrules")
    public Object[][] createMatchingRuleTest() {
        return new Object[][] {
            // \u00E9 = LATIN SMALL LETTER E WITH ACUTE
            // \u00C9 = LATIN CAPITAL LETTER E WITH ACUTE
            { "abc", "abd", ConditionResult.FALSE },
            { "abc", "acc", ConditionResult.FALSE },
            { "abc", "bbc", ConditionResult.FALSE },
            { "abc", "abD", ConditionResult.FALSE },
            { "def", "d\u00E9g", ConditionResult.FALSE },
            { "def", "dEg", ConditionResult.FALSE },
            { "dEf", "deg", ConditionResult.FALSE },
            { "d\u00E9f", "dEg", ConditionResult.FALSE },
            { "passe", "passe", ConditionResult.FALSE },
            { "passe ", "passe", ConditionResult.FALSE },
            { "passE", "passe", ConditionResult.FALSE },
            { "pass\u00E9", "passe", ConditionResult.FALSE },
            { "pass\u00E9", "passE", ConditionResult.FALSE },
            { "pass\u00E9", "pass\u00C9", ConditionResult.FALSE },
            { "passe", "pass\u00E9", ConditionResult.FALSE },
            { "passE", "pass\u00E9", ConditionResult.FALSE },
            { "pass\u00C9", "pass\u00E9", ConditionResult.FALSE },
            { "abd", "abc", ConditionResult.TRUE },
            { "acc", "abc", ConditionResult.TRUE },
            { "bbc", "abc", ConditionResult.TRUE },
            { "abD", "abc", ConditionResult.TRUE },
            { "d\u00E9g", "def", ConditionResult.TRUE },
            { "dEg", "def", ConditionResult.TRUE },
            { "deg", "dEf", ConditionResult.TRUE },
            { "dEg", "d\u00E9f", ConditionResult.TRUE },
        };
    }
    /**
     * {@inheritDoc}
     */
    @Override
    protected MatchingRule getRule() {
        // Note that oid and names are not used by the test (ie, they could be any value, test should pass anyway)
        // Only the implementation class and the provided locale are really tested here.
        String oid = "1.3.6.1.4.1.42.2.27.9.4.76.1.5";
        Schema schema = new SchemaBuilder(Schema.getCoreSchema()).
            buildMatchingRule(oid).
                syntaxOID(SchemaConstants.SYNTAX_DIRECTORY_STRING_OID).
                names("fr.gt").
                implementation(CollationMatchingRulesImpl.greaterThanMatchingRule(new Locale("fr"))).
                addToSchema().
            toSchema();
        return schema.getMatchingRule(oid);
    }
    @Test
    public void testCreateIndexQuery() throws Exception {
        ByteString value = ByteString.valueOf("abc");
        MatchingRule matchingRule = getRule();
        Assertion assertion = matchingRule.getAssertion(value);
        String indexQuery = assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions(), false));
        ByteString normalizedValue = matchingRule.normalizeAttributeValue(value);
        assertEquals(indexQuery, "rangeMatch(fr.shared, '" + normalizedValue.toHexString() + "' < value < '')");
    }
}
opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationGreaterThanOrEqualMatchingRuleTest.java
New file
@@ -0,0 +1,119 @@
/*
 * 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.Locale;
import org.forgerock.opendj.ldap.Assertion;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ConditionResult;
import org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.FakeIndexQueryFactory;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.*;
import static org.testng.Assert.*;
@SuppressWarnings("javadoc")
@Test
public class CollationGreaterThanOrEqualMatchingRuleTest extends MatchingRuleTest {
    /**
     * {@inheritDoc}
     */
    @Override
    @DataProvider(name = "matchingRuleInvalidAttributeValues")
    public Object[][] createMatchingRuleInvalidAttributeValues() {
        return new Object[][] { };
    }
    /**
     * {@inheritDoc}
     */
    @Override
    @DataProvider(name = "matchingrules")
    public Object[][] createMatchingRuleTest() {
        return new Object[][] {
            // \u00E9 = LATIN SMALL LETTER E WITH ACUTE
            // \u00C9 = LATIN CAPITAL LETTER E WITH ACUTE
            { "abc", "abd", ConditionResult.FALSE },
            { "abc", "acc", ConditionResult.FALSE },
            { "abc", "bbc", ConditionResult.FALSE },
            { "abc", "abD", ConditionResult.FALSE },
            { "def", "d\u00E9g", ConditionResult.FALSE },
            { "def", "dEg", ConditionResult.FALSE },
            { "dEf", "deg", ConditionResult.FALSE },
            { "d\u00E9f", "dEg", ConditionResult.FALSE },
            { "passe", "passe", ConditionResult.TRUE },
            { "passe ", "passe", ConditionResult.TRUE },
            { "passE", "passe", ConditionResult.TRUE },
            { "pass\u00E9", "passe", ConditionResult.TRUE },
            { "pass\u00E9", "passE", ConditionResult.TRUE },
            { "pass\u00E9", "pass\u00C9", ConditionResult.TRUE },
            { "passe", "pass\u00E9", ConditionResult.TRUE },
            { "passE", "pass\u00E9", ConditionResult.TRUE },
            { "pass\u00C9", "pass\u00E9", ConditionResult.TRUE },
            { "abd", "abc", ConditionResult.TRUE },
            { "acc", "abc", ConditionResult.TRUE },
            { "bbc", "abc", ConditionResult.TRUE },
            { "abD", "abc", ConditionResult.TRUE },
            { "d\u00E9g", "def", ConditionResult.TRUE },
            { "dEg", "def", ConditionResult.TRUE },
            { "deg", "dEf", ConditionResult.TRUE },
            { "dEg", "d\u00E9f", ConditionResult.TRUE },
        };
    }
    /**
     * {@inheritDoc}
     */
    @Override
    protected MatchingRule getRule() {
        // Note that oid and names are not used by the test (ie, they could be any value, test should pass anyway)
        // Only the implementation class and the provided locale are really tested here.
        String oid = "1.3.6.1.4.1.42.2.27.9.4.76.1.4";
        Schema schema = new SchemaBuilder(Schema.getCoreSchema()).
            buildMatchingRule(oid).
                syntaxOID(SchemaConstants.SYNTAX_DIRECTORY_STRING_OID).
                names("fr.gt2").
                implementation(CollationMatchingRulesImpl.greaterThanOrEqualToMatchingRule(new Locale("fr"))).
                addToSchema().
            toSchema();
        return schema.getMatchingRule(oid);
    }
    @Test
    public void testCreateIndexQuery() throws Exception {
        ByteString value = ByteString.valueOf("abc");
        MatchingRule matchingRule = getRule();
        Assertion assertion = matchingRule.getAssertion(value);
        String indexQuery = assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions(), false));
        ByteString normalizedValue = matchingRule.normalizeAttributeValue(value);
        assertEquals(indexQuery, "rangeMatch(fr.shared, '" + normalizedValue.toHexString() + "' <= value < '')");
    }
}
opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationLessThanMatchingRuleTest.java
New file
@@ -0,0 +1,119 @@
/*
 * 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.Locale;
import org.forgerock.opendj.ldap.Assertion;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ConditionResult;
import org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.FakeIndexQueryFactory;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.*;
import static org.testng.Assert.*;
@SuppressWarnings("javadoc")
@Test
public class CollationLessThanMatchingRuleTest extends MatchingRuleTest {
    /**
     * {@inheritDoc}
     */
    @Override
    @DataProvider(name = "matchingRuleInvalidAttributeValues")
    public Object[][] createMatchingRuleInvalidAttributeValues() {
        return new Object[][] { };
    }
    /**
     * {@inheritDoc}
     */
    @Override
    @DataProvider(name = "matchingrules")
    public Object[][] createMatchingRuleTest() {
        return new Object[][] {
            // \u00E9 = LATIN SMALL LETTER E WITH ACUTE
            // \u00C9 = LATIN CAPITAL LETTER E WITH ACUTE
            { "abc", "abd", ConditionResult.TRUE },
            { "abc", "acc", ConditionResult.TRUE },
            { "abc", "bbc", ConditionResult.TRUE },
            { "abc", "abD", ConditionResult.TRUE },
            { "def", "d\u00E9g", ConditionResult.TRUE },
            { "def", "dEg", ConditionResult.TRUE },
            { "dEf", "deg", ConditionResult.TRUE },
            { "d\u00E9f", "dEg", ConditionResult.TRUE },
            { "passe", "passe", ConditionResult.FALSE },
            { "passe ", "passe", ConditionResult.FALSE },
            { "passE", "passe", ConditionResult.FALSE },
            { "pass\u00E9", "passe", ConditionResult.FALSE },
            { "pass\u00E9", "passE", ConditionResult.FALSE },
            { "pass\u00E9", "pass\u00C9", ConditionResult.FALSE },
            { "passe", "pass\u00E9", ConditionResult.FALSE },
            { "passE", "pass\u00E9", ConditionResult.FALSE },
            { "pass\u00C9", "pass\u00E9", ConditionResult.FALSE },
            { "abd", "abc", ConditionResult.FALSE },
            { "acc", "abc", ConditionResult.FALSE },
            { "bbc", "abc", ConditionResult.FALSE },
            { "abD", "abc", ConditionResult.FALSE },
            { "d\u00E9g", "def", ConditionResult.FALSE },
            { "dEg", "def", ConditionResult.FALSE },
            { "deg", "dEf", ConditionResult.FALSE },
            { "dEg", "d\u00E9f", ConditionResult.FALSE },
        };
    }
    /**
     * {@inheritDoc}
     */
    @Override
    protected MatchingRule getRule() {
        // Note that oid and names are not used by the test (ie, they could be any value, test should pass anyway)
        // Only the implementation class and the provided locale are really tested here.
        String oid = "1.3.6.1.4.1.42.2.27.9.4.76.1.1";
        Schema schema = new SchemaBuilder(Schema.getCoreSchema()).
            buildMatchingRule(oid).
                syntaxOID(SchemaConstants.SYNTAX_DIRECTORY_STRING_OID).
                names("fr.lt").
                implementation(CollationMatchingRulesImpl.lessThanMatchingRule(new Locale("fr"))).
                addToSchema().
            toSchema();
        return schema.getMatchingRule(oid);
    }
    @Test
    public void testCreateIndexQuery() throws Exception {
        ByteString value = ByteString.valueOf("abc");
        MatchingRule matchingRule = getRule();
        Assertion assertion = matchingRule.getAssertion(value);
        String indexQuery = assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions(), false));
        ByteString normalizedValue = matchingRule.normalizeAttributeValue(value);
        assertEquals(indexQuery, "rangeMatch(fr.shared, '' < value < '" + normalizedValue.toHexString() + "')");
    }
}
opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationLessThanOrEqualMatchingRuleTest.java
New file
@@ -0,0 +1,119 @@
/*
 * 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.Locale;
import org.forgerock.opendj.ldap.Assertion;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ConditionResult;
import org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.FakeIndexQueryFactory;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.*;
import static org.testng.Assert.*;
@SuppressWarnings("javadoc")
@Test
public class CollationLessThanOrEqualMatchingRuleTest extends MatchingRuleTest {
    /**
     * {@inheritDoc}
     */
    @Override
    @DataProvider(name = "matchingRuleInvalidAttributeValues")
    public Object[][] createMatchingRuleInvalidAttributeValues() {
        return new Object[][] { };
    }
    /**
     * {@inheritDoc}
     */
    @Override
    @DataProvider(name = "matchingrules")
    public Object[][] createMatchingRuleTest() {
        return new Object[][] {
            // \u00E9 = LATIN SMALL LETTER E WITH ACUTE
            // \u00C9 = LATIN CAPITAL LETTER E WITH ACUTE
            { "abc", "abd", ConditionResult.TRUE },
            { "abc", "acc", ConditionResult.TRUE },
            { "abc", "bbc", ConditionResult.TRUE },
            { "abc", "abD", ConditionResult.TRUE },
            { "def", "d\u00E9g", ConditionResult.TRUE },
            { "def", "dEg", ConditionResult.TRUE },
            { "dEf", "deg", ConditionResult.TRUE },
            { "d\u00E9f", "dEg", ConditionResult.TRUE },
            { "passe", "passe", ConditionResult.TRUE },
            { "passe ", "passe", ConditionResult.TRUE },
            { "passE", "passe", ConditionResult.TRUE },
            { "pass\u00E9", "passe", ConditionResult.TRUE },
            { "pass\u00E9", "passE", ConditionResult.TRUE },
            { "pass\u00E9", "pass\u00C9", ConditionResult.TRUE },
            { "passe", "pass\u00E9", ConditionResult.TRUE },
            { "passE", "pass\u00E9", ConditionResult.TRUE },
            { "pass\u00C9", "pass\u00E9", ConditionResult.TRUE },
            { "abd", "abc", ConditionResult.FALSE },
            { "acc", "abc", ConditionResult.FALSE },
            { "bbc", "abc", ConditionResult.FALSE },
            { "abD", "abc", ConditionResult.FALSE },
            { "d\u00E9g", "def", ConditionResult.FALSE },
            { "dEg", "def", ConditionResult.FALSE },
            { "deg", "dEf", ConditionResult.FALSE },
            { "dEg", "d\u00E9f", ConditionResult.FALSE },
        };
    }
    /**
     * {@inheritDoc}
     */
    @Override
    protected MatchingRule getRule() {
        // Note that oid and names are not used by the test (ie, they could be any value, test should pass anyway)
        // Only the implementation class and the provided locale are really tested here.
        String oid = "1.3.6.1.4.1.42.2.27.9.4.76.1.2";
        Schema schema = new SchemaBuilder(Schema.getCoreSchema()).
            buildMatchingRule(oid).
                syntaxOID(SchemaConstants.SYNTAX_DIRECTORY_STRING_OID).
                names("fr.lte").
                implementation(CollationMatchingRulesImpl.lessThanOrEqualMatchingRule(new Locale("fr"))).
                addToSchema().
            toSchema();
        return schema.getMatchingRule(oid);
    }
    @Test
    public void testCreateIndexQuery() throws Exception {
        ByteString value = ByteString.valueOf("abc");
        MatchingRule matchingRule = getRule();
        Assertion assertion = matchingRule.getAssertion(value);
        String indexQuery = assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions(), false));
        ByteString normalizedValue = matchingRule.normalizeAttributeValue(value);
        assertEquals(indexQuery, "rangeMatch(fr.shared, '' < value <= '" + normalizedValue.toHexString() + "')");
    }
}
opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationSubstringMatchingRuleTest.java
New file
@@ -0,0 +1,187 @@
/*
 * 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.Locale;
import org.forgerock.opendj.ldap.Assertion;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ConditionResult;
import org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.FakeIndexQueryFactory;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.*;
import static org.testng.Assert.*;
@SuppressWarnings("javadoc")
@Test
public class CollationSubstringMatchingRuleTest extends SubstringMatchingRuleTest {
    @DataProvider(name = "substringInvalidAssertionValues")
    public Object[][] createMatchingRuleInvalidAssertionValues() {
        return new Object[][] { };
    }
    @DataProvider(name = "substringInvalidAttributeValues")
    public Object[][] createMatchingRuleInvalidAttributeValues() {
        return new Object[][] { };
    }
    /**
     * {@inheritDoc}
     */
    @Override
    @DataProvider(name = "substringFinalMatchData")
    public Object[][] createSubstringFinalMatchData() {
        return new Object[][] {
            { "this is a value", "value", ConditionResult.TRUE },
            { "this is a value", "alue", ConditionResult.TRUE },
            { "this is a value", "ue", ConditionResult.TRUE },
            { "this is a value", "e", ConditionResult.TRUE },
            { "this is a value", "valu", ConditionResult.FALSE },
            { "this is a value", "this", ConditionResult.FALSE },
            { "this is a value", "VALUE", ConditionResult.TRUE },
            { "this is a value", "AlUe", ConditionResult.TRUE },
            { "this is a value", "UE", ConditionResult.TRUE },
            { "this is a value", "E", ConditionResult.TRUE },
            { "this is a value", "THIS", ConditionResult.FALSE },
            { "this is a value", " ", ConditionResult.TRUE },
            { "this is a VALUE", "value", ConditionResult.TRUE },
            { "end with space    ", " ", ConditionResult.TRUE },
            { "end with space    ", "space", ConditionResult.TRUE },
            { "end with space    ", "SPACE", ConditionResult.TRUE },
            // \u00E9 = LATIN SMALL LETTER E WITH ACUTE
            // \u00C9 = LATIN CAPITAL LETTER E WITH ACUTE
            { "il est passe", "passE", ConditionResult.TRUE },
            { "il est passe", "pass\u00E9", ConditionResult.TRUE },
            { "il est passe", "pass\u00C9", ConditionResult.TRUE },
            { "il est pass\u00C9", "passe", ConditionResult.TRUE },
            { "il est pass\u00E9", "passE", ConditionResult.TRUE },
        };
    }
    /**
     * {@inheritDoc}
     */
    @Override
    @DataProvider(name = "substringInitialMatchData")
    public Object[][] createSubstringInitialMatchData() {
        return new Object[][] {
            { "this is a value", "this", ConditionResult.TRUE },
            { "this is a value", "th", ConditionResult.TRUE },
            { "this is a value", "t", ConditionResult.TRUE },
            { "this is a value", "is", ConditionResult.FALSE },
            { "this is a value", "a", ConditionResult.FALSE },
            { "this is a value", "TH", ConditionResult.TRUE },
            { "this is a value", "T", ConditionResult.TRUE },
            { "this is a value", "IS", ConditionResult.FALSE },
            { "this is a value", "A", ConditionResult.FALSE },
            { "this is a value", "VALUE", ConditionResult.FALSE },
            { "this is a value", " ", ConditionResult.TRUE },
            { "this is a value", "NOT", ConditionResult.FALSE },
            { "this is a value", "THIS", ConditionResult.TRUE },
            // \u00E9 = LATIN SMALL LETTER E WITH ACUTE
            // \u00C9 = LATIN CAPITAL LETTER E WITH ACUTE
            { "il etait passe", "Il \u00E9", ConditionResult.TRUE },
            { "il etait passe", "Il \u00C9", ConditionResult.TRUE },
            { "il etait passe", "Il E", ConditionResult.TRUE },
            { "il \u00E9tait passe", "IL e", ConditionResult.TRUE },
        };
    }
    /**
     * {@inheritDoc}
     */
    @Override
    @DataProvider(name = "substringMiddleMatchData")
    public Object[][] createSubstringMiddleMatchData() {
        return new Object[][] {
            { "this is a value", strings("this"), ConditionResult.TRUE },
            { "this is a value", strings("is"), ConditionResult.TRUE },
            { "this is a value", strings("a"), ConditionResult.TRUE },
            { "this is a value", strings("value"), ConditionResult.TRUE },
            { "this is a value", strings("THIS"), ConditionResult.TRUE },
            { "this is a value", strings("IS"), ConditionResult.TRUE },
            { "this is a value", strings("A"), ConditionResult.TRUE },
            { "this is a value", strings("VALUE"), ConditionResult.TRUE },
            { "this is a value", strings(" "), ConditionResult.TRUE },
            { "this is a value", strings("this", "is", "a", "value"), ConditionResult.TRUE },
            // The matching rule requires ordered non overlapping substrings.
            // Issue #730 was not valid.
            { "this is a value", strings("value", "this"), ConditionResult.FALSE },
            { "this is a value", strings("this", "this is"), ConditionResult.FALSE },
            { "this is a value", strings("this", "IS", "a", "VALue"), ConditionResult.TRUE },
            { "this is a value", strings("his IS", "A val"), ConditionResult.TRUE },
            { "this is a value", strings("not"), ConditionResult.FALSE },
            { "this is a value", strings("this", "not"), ConditionResult.FALSE },
            { "this is a value", strings("    "), ConditionResult.TRUE },
            // \u00E9 = LATIN SMALL LETTER E WITH ACUTE
            // \u00C9 = LATIN CAPITAL LETTER E WITH ACUTE
            { "il est passe par la", strings("est", "pass\u00E9"), ConditionResult.TRUE },
            { "il est passe par la", strings("pass\u00E9", "Par"), ConditionResult.TRUE },
            { "il est passe par la", strings("est", "pass\u00C9", "PAR", "La"), ConditionResult.TRUE },
            { "il est pass\u00E9 par la", strings("il", "Est", "pass\u00C9"), ConditionResult.TRUE },
            { "il est pass\u00C9 par la", strings("est", "passe"), ConditionResult.TRUE },
        };
    }
    /**
     * {@inheritDoc}
     */
    @Override
    protected MatchingRule getRule() {
        // Note that oid and names are not used by the test (ie, they could be any value, test should pass anyway)
        // Only the implementation class and the provided locale are really tested here.
        String oid = "1.3.6.1.4.1.42.2.27.9.4.76.1.6";
        Schema schema = new SchemaBuilder(Schema.getCoreSchema()).
            buildMatchingRule(oid).
                syntaxOID(SchemaConstants.SYNTAX_DIRECTORY_STRING_OID).
                names("fr.sub").
                implementation(CollationMatchingRulesImpl.substringMatchingRule(new Locale("fr"))).
                addToSchema().
            toSchema();
        return schema.getMatchingRule(oid);
    }
    @Test
    public void testCreateIndexQuery() throws Exception {
        ByteString value = ByteString.valueOf("a*c");
        MatchingRule matchingRule = getRule();
        Assertion assertion = matchingRule.getAssertion(value);
        String indexQuery = assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions(), false));
        ByteString binit = matchingRule.normalizeAttributeValue(ByteString.valueOf("a"));
        ByteString bfinal = matchingRule.normalizeAttributeValue(ByteString.valueOf("c"));
        assertEquals(indexQuery,
            "intersect["
            + "rangeMatch(fr.shared, '" + binit.toHexString() + "' <= value < '00 54'), "
            + "rangeMatch(fr.substring, '" + bfinal.toHexString() + "' <= value < '00 56'), "
            + "rangeMatch(fr.substring, '" + binit.toHexString() + "' <= value < '00 54')]"
        );
    }
}