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

Nicolas Capponi
07.23.2014 7de8bea3dd0f43507546127467e12ab6266cf4dc
OPENDJ-1590 Migrate collation matching rules

Add collation matching rules implementations in the SDK

Add support for custom naming of indexes in
AbstractOrderingMatchingRuleImpl and
AbstractSubstringMatchingRuleImpl classes

Add unit tests for collation matching rules implementations
7 files added
5 files modified
1299 ■■■■■ changed files
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractEqualityMatchingRuleImpl.java 3 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractMatchingRuleImpl.java 10 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractOrderingMatchingRuleImpl.java 52 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImpl.java 57 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CollationMatchingRulesImpl.java 365 ●●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImplTest.java 29 ●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationEqualityMatchingRuleTest.java 120 ●●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationGreaterThanMatchingRuleTest.java 119 ●●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationGreaterThanOrEqualMatchingRuleTest.java 119 ●●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationLessThanMatchingRuleTest.java 119 ●●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationLessThanOrEqualMatchingRuleTest.java 119 ●●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationSubstringMatchingRuleTest.java 187 ●●●●● patch | view | raw | blame | history
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-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-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-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-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-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-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-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-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-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-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-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')]"
        );
    }
}