001    /*
002     * Copyright 2007-2017 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2017 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.ldap.matchingrules;
022    
023    
024    
025    import java.io.Serializable;
026    import java.lang.reflect.Method;
027    
028    import com.unboundid.asn1.ASN1OctetString;
029    import com.unboundid.ldap.sdk.LDAPException;
030    import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
031    import com.unboundid.ldap.sdk.schema.Schema;
032    import com.unboundid.util.Debug;
033    import com.unboundid.util.Extensible;
034    import com.unboundid.util.ThreadSafety;
035    import com.unboundid.util.ThreadSafetyLevel;
036    
037    import static com.unboundid.util.StaticUtils.*;
038    
039    
040    
041    /**
042     * This class defines the API for an LDAP matching rule, which may be used to
043     * determine whether two values are equal to each other, and to normalize values
044     * so that they may be more easily compared.
045     */
046    @Extensible()
047    @ThreadSafety(level=ThreadSafetyLevel.INTERFACE_THREADSAFE)
048    public abstract class MatchingRule
049           implements Serializable
050    {
051      /**
052       * The substring element type used for subInitial substring assertion
053       * components.
054       */
055      public static final byte SUBSTRING_TYPE_SUBINITIAL = (byte) 0x80;
056    
057    
058    
059      /**
060       * The substring element type used for subAny substring assertion components.
061       */
062      public static final byte SUBSTRING_TYPE_SUBANY = (byte) 0x81;
063    
064    
065    
066      /**
067       * The substring element type used for subFinal substring assertion
068       * components.
069       */
070      public static final byte SUBSTRING_TYPE_SUBFINAL = (byte) 0x82;
071    
072    
073    
074      /**
075       * A reference to the jsonObjectExactMatch matching rule, if it has been
076       * loaded.  This matching rule needs to be loaded via reflection because it's
077       * only included in the Commercial Edition and isn't part of the Standard
078       * Edition.
079       */
080      private static volatile MatchingRule jsonObjectExactMatchingRule = null;
081    
082    
083    
084      /**
085       * The serial version UID for this serializable class.
086       */
087      private static final long serialVersionUID = 6050276733546358513L;
088    
089    
090    
091      /**
092       * Creates a new instance of this matching rule.
093       */
094      protected MatchingRule()
095      {
096        // No implementation is required.
097      }
098    
099    
100    
101      /**
102       * Retrieves the name for this matching rule when used to perform equality
103       * matching, if appropriate.
104       *
105       * @return  The name for this matching rule when used to perform equality
106       *          matching, or {@code null} if this matching rule is not intended
107       *          to be used for equality matching.
108       */
109      public abstract String getEqualityMatchingRuleName();
110    
111    
112    
113      /**
114       * Retrieves the OID for this matching rule when used to perform equality
115       * matching, if appropriate.
116       *
117       * @return  The OID for this matching rule when used to perform equality
118       *          matching, or {@code null} if this matching rule is not intended
119       *          to be used for equality matching.
120       */
121      public abstract String getEqualityMatchingRuleOID();
122    
123    
124    
125      /**
126       * Retrieves the name for this matching rule when used to perform equality
127       * matching if defined, or the OID if no name is available.
128       *
129       * @return  The name or OID for this matching rule when used to perform
130       *          equality matching, or {@code null} if this matching rule cannot
131       *          be used to perform equality matching.
132       */
133      public String getEqualityMatchingRuleNameOrOID()
134      {
135        final String name = getEqualityMatchingRuleName();
136        if (name == null)
137        {
138          return getEqualityMatchingRuleOID();
139        }
140        else
141        {
142          return name;
143        }
144      }
145    
146    
147    
148      /**
149       * Retrieves the name for this matching rule when used to perform ordering
150       * matching, if appropriate.
151       *
152       * @return  The name for this matching rule when used to perform ordering
153       *          matching, or {@code null} if this matching rule is not intended
154       *          to be used for ordering matching.
155       */
156      public abstract String getOrderingMatchingRuleName();
157    
158    
159    
160      /**
161       * Retrieves the OID for this matching rule when used to perform ordering
162       * matching, if appropriate.
163       *
164       * @return  The OID for this matching rule when used to perform ordering
165       *          matching, or {@code null} if this matching rule is not intended
166       *          to be used for ordering matching.
167       */
168      public abstract String getOrderingMatchingRuleOID();
169    
170    
171    
172      /**
173       * Retrieves the name for this matching rule when used to perform ordering
174       * matching if defined, or the OID if no name is available.
175       *
176       * @return  The name or OID for this matching rule when used to perform
177       *          ordering matching, or {@code null} if this matching rule cannot
178       *          be used to perform equality matching.
179       */
180      public String getOrderingMatchingRuleNameOrOID()
181      {
182        final String name = getOrderingMatchingRuleName();
183        if (name == null)
184        {
185          return getOrderingMatchingRuleOID();
186        }
187        else
188        {
189          return name;
190        }
191      }
192    
193    
194    
195      /**
196       * Retrieves the name for this matching rule when used to perform substring
197       * matching, if appropriate.
198       *
199       * @return  The name for this matching rule when used to perform substring
200       *          matching, or {@code null} if this matching rule is not intended
201       *          to be used for substring matching.
202       */
203      public abstract String getSubstringMatchingRuleName();
204    
205    
206    
207      /**
208       * Retrieves the OID for this matching rule when used to perform substring
209       * matching, if appropriate.
210       *
211       * @return  The OID for this matching rule when used to perform substring
212       *          matching, or {@code null} if this matching rule is not intended
213       *          to be used for substring matching.
214       */
215      public abstract String getSubstringMatchingRuleOID();
216    
217    
218    
219      /**
220       * Retrieves the name for this matching rule when used to perform substring
221       * matching if defined, or the OID if no name is available.
222       *
223       * @return  The name or OID for this matching rule when used to perform
224       *          substring matching, or {@code null} if this matching rule cannot
225       *          be used to perform equality matching.
226       */
227      public String getSubstringMatchingRuleNameOrOID()
228      {
229        final String name = getSubstringMatchingRuleName();
230        if (name == null)
231        {
232          return getSubstringMatchingRuleOID();
233        }
234        else
235        {
236          return name;
237        }
238      }
239    
240    
241    
242      /**
243       * Indicates whether the provided values are equal to each other, according to
244       * the constraints of this matching rule.
245       *
246       * @param  value1  The first value for which to make the determination.
247       * @param  value2  The second value for which to make the determination.
248       *
249       * @return  {@code true} if the provided values are considered equal, or
250       *          {@code false} if not.
251       *
252       * @throws  LDAPException  If a problem occurs while making the determination,
253       *                         or if this matching rule does not support equality
254       *                         matching.
255       */
256      public abstract boolean valuesMatch(final ASN1OctetString value1,
257                                          final ASN1OctetString value2)
258             throws LDAPException;
259    
260    
261    
262      /**
263       * Indicates whether the provided value matches the given substring assertion,
264       * according to the constraints of this matching rule.
265       *
266       * @param  value       The value for which to make the determination.
267       * @param  subInitial  The subInitial portion of the substring assertion, or
268       *                     {@code null} if there is no subInitial element.
269       * @param  subAny      The subAny elements of the substring assertion, or
270       *                     {@code null} if there are no subAny elements.
271       * @param  subFinal    The subFinal portion of the substring assertion, or
272       *                     {@code null} if there is no subFinal element.
273       *
274       * @return  {@code true} if the provided value matches the substring
275       *          assertion, or {@code false} if not.
276       *
277       * @throws  LDAPException  If a problem occurs while making the determination,
278       *                         or if this matching rule does not support substring
279       *                         matching.
280       */
281      public abstract boolean matchesSubstring(final ASN1OctetString value,
282                                               final ASN1OctetString subInitial,
283                                               final ASN1OctetString[] subAny,
284                                               final ASN1OctetString subFinal)
285             throws LDAPException;
286    
287    
288    
289      /**
290       * Compares the provided values to determine their relative order in a sorted
291       * list.
292       *
293       * @param  value1  The first value to compare.
294       * @param  value2  The second value to compare.
295       *
296       * @return  A negative value if {@code value1} should come before
297       *          {@code value2} in a sorted list, a positive value if
298       *          {@code value1} should come after {@code value2} in a sorted list,
299       *          or zero if the values are equal or there is no distinction between
300       *          their orders in a sorted list.
301       *
302       * @throws  LDAPException  If a problem occurs while making the determination,
303       *                         or if this matching rule does not support ordering
304       *                         matching.
305       */
306      public abstract int compareValues(final ASN1OctetString value1,
307                                        final ASN1OctetString value2)
308             throws LDAPException;
309    
310    
311    
312      /**
313       * Normalizes the provided value for easier matching.
314       *
315       * @param  value  The value to be normalized.
316       *
317       * @return  The normalized form of the provided value.
318       *
319       * @throws  LDAPException  If a problem occurs while normalizing the provided
320       *                         value.
321       */
322      public abstract ASN1OctetString normalize(final ASN1OctetString value)
323             throws LDAPException;
324    
325    
326    
327      /**
328       * Normalizes the provided value for use as part of a substring assertion.
329       *
330       * @param  value          The value to be normalized for use as part of a
331       *                        substring assertion.
332       * @param  substringType  The substring assertion component type for the
333       *                        provided value.  It should be one of
334       *                        {@code SUBSTRING_TYPE_SUBINITIAL},
335       *                        {@code SUBSTRING_TYPE_SUBANY}, or
336       *                        {@code SUBSTRING_TYPE_SUBFINAL}.
337       *
338       * @return  The normalized form of the provided value.
339       *
340       * @throws  LDAPException  If a problem occurs while normalizing the provided
341       *                         value.
342       */
343      public abstract ASN1OctetString normalizeSubstring(
344                                           final ASN1OctetString value,
345                                           final byte substringType)
346             throws LDAPException;
347    
348    
349    
350      /**
351       * Attempts to select the appropriate matching rule to use for equality
352       * matching against the specified attribute.  If an appropriate matching rule
353       * cannot be determined, then the default equality matching rule will be
354       * selected.
355       *
356       * @param  attrName  The name of the attribute to examine in the provided
357       *                   schema.
358       * @param  schema    The schema to examine to make the appropriate
359       *                   determination.  If this is {@code null}, then the default
360       *                   equality matching rule will be selected.
361       *
362       * @return  The selected matching rule.
363       */
364      public static MatchingRule selectEqualityMatchingRule(final String attrName,
365                                                            final Schema schema)
366      {
367        return selectEqualityMatchingRule(attrName, null, schema);
368      }
369    
370    
371    
372      /**
373       * Attempts to select the appropriate matching rule to use for equality
374       * matching against the specified attribute.  If an appropriate matching rule
375       * cannot be determined, then the default equality matching rule will be
376       * selected.
377       *
378       * @param  attrName  The name of the attribute to examine in the provided
379       *                   schema.  It may be {@code null} if the matching rule
380       *                   should be selected using the matching rule ID.
381       * @param  ruleID    The OID of the desired matching rule.  It may be
382       *                   {@code null} if the matching rule should be selected only
383       *                   using the attribute name.  If a rule ID is provided, then
384       *                   it will be the only criteria used to select the matching
385       *                   rule.
386       * @param  schema    The schema to examine to make the appropriate
387       *                   determination.  If this is {@code null} and no rule ID
388       *                   was provided, then the default equality matching rule
389       *                   will be selected.
390       *
391       * @return  The selected matching rule.
392       */
393      public static MatchingRule selectEqualityMatchingRule(final String attrName,
394                                      final String ruleID, final Schema schema)
395      {
396        if (ruleID != null)
397        {
398          return selectEqualityMatchingRule(ruleID);
399        }
400    
401        if ((attrName == null) || (schema == null))
402        {
403          return getDefaultEqualityMatchingRule();
404        }
405    
406        final AttributeTypeDefinition attrType = schema.getAttributeType(attrName);
407        if (attrType == null)
408        {
409          return getDefaultEqualityMatchingRule();
410        }
411    
412        final String mrName = attrType.getEqualityMatchingRule(schema);
413        if (mrName != null)
414        {
415          return selectEqualityMatchingRule(mrName);
416        }
417    
418        final String syntaxOID = attrType.getBaseSyntaxOID(schema);
419        if (syntaxOID != null)
420        {
421          return selectMatchingRuleForSyntax(syntaxOID);
422        }
423    
424        return getDefaultEqualityMatchingRule();
425      }
426    
427    
428    
429      /**
430       * Attempts to select the appropriate matching rule to use for equality
431       * matching using the specified matching rule.  If an appropriate matching
432       * rule cannot be determined, then the default equality matching rule will be
433       * selected.
434       *
435       * @param  ruleID  The name or OID of the desired matching rule.
436       *
437       * @return  The selected matching rule.
438       */
439      public static MatchingRule selectEqualityMatchingRule(final String ruleID)
440      {
441        if ((ruleID == null) || (ruleID.length() == 0))
442        {
443          return getDefaultEqualityMatchingRule();
444        }
445    
446        final String lowerName = toLowerCase(ruleID);
447        if (lowerName.equals(BooleanMatchingRule.LOWER_EQUALITY_RULE_NAME) ||
448            lowerName.equals(BooleanMatchingRule.EQUALITY_RULE_OID))
449        {
450          return BooleanMatchingRule.getInstance();
451        }
452        else if (lowerName.equals(
453                      CaseExactStringMatchingRule.LOWER_EQUALITY_RULE_NAME) ||
454                 lowerName.equals(CaseExactStringMatchingRule.EQUALITY_RULE_OID) ||
455                 lowerName.equals("caseexactia5match") ||
456                 lowerName.equals("1.3.6.1.4.1.1466.109.114.1"))
457        {
458          return CaseExactStringMatchingRule.getInstance();
459        }
460        else if (lowerName.equals(
461                      CaseIgnoreListMatchingRule.LOWER_EQUALITY_RULE_NAME) ||
462                 lowerName.equals(CaseIgnoreListMatchingRule.EQUALITY_RULE_OID))
463        {
464          return CaseIgnoreListMatchingRule.getInstance();
465        }
466        else if (lowerName.equals(
467                      CaseIgnoreStringMatchingRule.LOWER_EQUALITY_RULE_NAME) ||
468                 lowerName.equals(CaseIgnoreStringMatchingRule.EQUALITY_RULE_OID) ||
469                 lowerName.equals("caseignoreia5match") ||
470                 lowerName.equals("1.3.6.1.4.1.1466.109.114.2"))
471        {
472          return CaseIgnoreStringMatchingRule.getInstance();
473        }
474        else if (lowerName.equals(
475                      DistinguishedNameMatchingRule.LOWER_EQUALITY_RULE_NAME) ||
476                 lowerName.equals(
477                      DistinguishedNameMatchingRule.EQUALITY_RULE_OID) ||
478                 lowerName.equals("uniquemembermatch") ||
479                 lowerName.equals("2.5.13.23"))
480        {
481          // NOTE -- Technically uniqueMember should use a name and optional UID
482          // matching rule, but the SDK doesn't currently provide one and the
483          // distinguished name matching rule should be sufficient the vast
484          // majority of the time.
485          return DistinguishedNameMatchingRule.getInstance();
486        }
487        else if (lowerName.equals(
488                      GeneralizedTimeMatchingRule.LOWER_EQUALITY_RULE_NAME) ||
489                 lowerName.equals(GeneralizedTimeMatchingRule.EQUALITY_RULE_OID))
490        {
491          return GeneralizedTimeMatchingRule.getInstance();
492        }
493        else if (lowerName.equals(IntegerMatchingRule.LOWER_EQUALITY_RULE_NAME) ||
494                 lowerName.equals(IntegerMatchingRule.EQUALITY_RULE_OID))
495        {
496          return IntegerMatchingRule.getInstance();
497        }
498        else if (lowerName.equals(
499                      NumericStringMatchingRule.LOWER_EQUALITY_RULE_NAME) ||
500                 lowerName.equals(NumericStringMatchingRule.EQUALITY_RULE_OID))
501        {
502          return NumericStringMatchingRule.getInstance();
503        }
504        else if (lowerName.equals(
505                      OctetStringMatchingRule.LOWER_EQUALITY_RULE_NAME) ||
506                 lowerName.equals(OctetStringMatchingRule.EQUALITY_RULE_OID))
507        {
508          return OctetStringMatchingRule.getInstance();
509        }
510        else if (lowerName.equals(
511                      TelephoneNumberMatchingRule.LOWER_EQUALITY_RULE_NAME) ||
512                 lowerName.equals(TelephoneNumberMatchingRule.EQUALITY_RULE_OID))
513        {
514          return TelephoneNumberMatchingRule.getInstance();
515        }
516        else if (lowerName.equals("jsonobjectexactmatch") ||
517                 lowerName.equals("1.3.6.1.4.1.30221.2.4.12"))
518        {
519          return getJSONObjectExactMatchingRule();
520        }
521        else
522        {
523          return getDefaultEqualityMatchingRule();
524        }
525      }
526    
527    
528    
529      /**
530       * Retrieves the default matching rule that will be used for equality matching
531       * if no other matching rule is specified or available.  The rule returned
532       * will perform case-ignore string matching.
533       *
534       * @return  The default matching rule that will be used for equality matching
535       *          if no other matching rule is specified or available.
536       */
537      public static MatchingRule getDefaultEqualityMatchingRule()
538      {
539        return CaseIgnoreStringMatchingRule.getInstance();
540      }
541    
542    
543    
544      /**
545       * Attempts to select the appropriate matching rule to use for ordering
546       * matching against the specified attribute.  If an appropriate matching rule
547       * cannot be determined, then the default ordering matching rule will be
548       * selected.
549       *
550       * @param  attrName  The name of the attribute to examine in the provided
551       *                   schema.
552       * @param  schema    The schema to examine to make the appropriate
553       *                   determination.  If this is {@code null}, then the default
554       *                   ordering matching rule will be selected.
555       *
556       * @return  The selected matching rule.
557       */
558      public static MatchingRule selectOrderingMatchingRule(final String attrName,
559                                                            final Schema schema)
560      {
561        return selectOrderingMatchingRule(attrName, null, schema);
562      }
563    
564    
565    
566      /**
567       * Attempts to select the appropriate matching rule to use for ordering
568       * matching against the specified attribute.  If an appropriate matching rule
569       * cannot be determined, then the default ordering matching rule will be
570       * selected.
571       *
572       * @param  attrName  The name of the attribute to examine in the provided
573       *                   schema.  It may be {@code null} if the matching rule
574       *                   should be selected using the matching rule ID.
575       * @param  ruleID    The OID of the desired matching rule.  It may be
576       *                   {@code null} if the matching rule should be selected only
577       *                   using the attribute name.  If a rule ID is provided, then
578       *                   it will be the only criteria used to select the matching
579       *                   rule.
580       * @param  schema    The schema to examine to make the appropriate
581       *                   determination.  If this is {@code null} and no rule ID
582       *                   was provided, then the default ordering matching rule
583       *                   will be selected.
584       *
585       * @return  The selected matching rule.
586       */
587      public static MatchingRule selectOrderingMatchingRule(final String attrName,
588                                                            final String ruleID,
589                                                            final Schema schema)
590      {
591        if (ruleID != null)
592        {
593          return selectOrderingMatchingRule(ruleID);
594        }
595    
596        if ((attrName == null) || (schema == null))
597        {
598          return getDefaultOrderingMatchingRule();
599        }
600    
601        final AttributeTypeDefinition attrType = schema.getAttributeType(attrName);
602        if (attrType == null)
603        {
604          return getDefaultOrderingMatchingRule();
605        }
606    
607        final String mrName = attrType.getOrderingMatchingRule(schema);
608        if (mrName != null)
609        {
610          return selectOrderingMatchingRule(mrName);
611        }
612    
613        final String syntaxOID = attrType.getBaseSyntaxOID(schema);
614        if (syntaxOID != null)
615        {
616          return selectMatchingRuleForSyntax(syntaxOID);
617        }
618    
619        return getDefaultOrderingMatchingRule();
620      }
621    
622    
623    
624      /**
625       * Attempts to select the appropriate matching rule to use for ordering
626       * matching using the specified matching rule.  If an appropriate matching
627       * rule cannot be determined, then the default ordering matching rule will be
628       * selected.
629       *
630       * @param  ruleID  The name or OID of the desired matching rule.
631       *
632       * @return  The selected matching rule.
633       */
634      public static MatchingRule selectOrderingMatchingRule(final String ruleID)
635      {
636        if ((ruleID == null) || (ruleID.length() == 0))
637        {
638          return getDefaultOrderingMatchingRule();
639        }
640    
641        final String lowerName = toLowerCase(ruleID);
642        if (lowerName.equals(
643                 CaseExactStringMatchingRule.LOWER_ORDERING_RULE_NAME) ||
644            lowerName.equals(CaseExactStringMatchingRule.ORDERING_RULE_OID))
645        {
646          return CaseExactStringMatchingRule.getInstance();
647        }
648        else if (lowerName.equals(
649                      CaseIgnoreStringMatchingRule.LOWER_ORDERING_RULE_NAME) ||
650                 lowerName.equals(CaseIgnoreStringMatchingRule.ORDERING_RULE_OID))
651        {
652          return CaseIgnoreStringMatchingRule.getInstance();
653        }
654        else if (lowerName.equals(
655                      GeneralizedTimeMatchingRule.LOWER_ORDERING_RULE_NAME) ||
656                 lowerName.equals(GeneralizedTimeMatchingRule.ORDERING_RULE_OID))
657        {
658          return GeneralizedTimeMatchingRule.getInstance();
659        }
660        else if (lowerName.equals(IntegerMatchingRule.LOWER_ORDERING_RULE_NAME) ||
661                 lowerName.equals(IntegerMatchingRule.ORDERING_RULE_OID))
662        {
663          return IntegerMatchingRule.getInstance();
664        }
665        else if (lowerName.equals(
666                      NumericStringMatchingRule.LOWER_ORDERING_RULE_NAME) ||
667                 lowerName.equals(NumericStringMatchingRule.ORDERING_RULE_OID))
668        {
669          return NumericStringMatchingRule.getInstance();
670        }
671        else if (lowerName.equals(
672                      OctetStringMatchingRule.LOWER_ORDERING_RULE_NAME) ||
673                 lowerName.equals(OctetStringMatchingRule.ORDERING_RULE_OID))
674        {
675          return OctetStringMatchingRule.getInstance();
676        }
677        else
678        {
679          return getDefaultOrderingMatchingRule();
680        }
681      }
682    
683    
684    
685      /**
686       * Retrieves the default matching rule that will be used for ordering matching
687       * if no other matching rule is specified or available.  The rule returned
688       * will perform case-ignore string matching.
689       *
690       * @return  The default matching rule that will be used for ordering matching
691       *          if no other matching rule is specified or available.
692       */
693      public static MatchingRule getDefaultOrderingMatchingRule()
694      {
695        return CaseIgnoreStringMatchingRule.getInstance();
696      }
697    
698    
699    
700      /**
701       * Attempts to select the appropriate matching rule to use for substring
702       * matching against the specified attribute.  If an appropriate matching rule
703       * cannot be determined, then the default substring matching rule will be
704       * selected.
705       *
706       * @param  attrName  The name of the attribute to examine in the provided
707       *                   schema.
708       * @param  schema    The schema to examine to make the appropriate
709       *                   determination.  If this is {@code null}, then the default
710       *                   substring matching rule will be selected.
711       *
712       * @return  The selected matching rule.
713       */
714      public static MatchingRule selectSubstringMatchingRule(final String attrName,
715                                                             final Schema schema)
716      {
717        return selectSubstringMatchingRule(attrName, null, schema);
718      }
719    
720    
721    
722      /**
723       * Attempts to select the appropriate matching rule to use for substring
724       * matching against the specified attribute.  If an appropriate matching rule
725       * cannot be determined, then the default substring matching rule will be
726       * selected.
727       *
728       * @param  attrName  The name of the attribute to examine in the provided
729       *                   schema.  It may be {@code null} if the matching rule
730       *                   should be selected using the matching rule ID.
731       * @param  ruleID    The OID of the desired matching rule.  It may be
732       *                   {@code null} if the matching rule should be selected only
733       *                   using the attribute name.  If a rule ID is provided, then
734       *                   it will be the only criteria used to select the matching
735       *                   rule.
736       * @param  schema    The schema to examine to make the appropriate
737       *                   determination.  If this is {@code null} and no rule ID
738       *                   was provided, then the default substring matching rule
739       *                   will be selected.
740       *
741       * @return  The selected matching rule.
742       */
743      public static MatchingRule selectSubstringMatchingRule(final String attrName,
744                                                             final String ruleID,
745                                                             final Schema schema)
746      {
747        if (ruleID != null)
748        {
749          return selectSubstringMatchingRule(ruleID);
750        }
751    
752        if ((attrName == null) || (schema == null))
753        {
754          return getDefaultSubstringMatchingRule();
755        }
756    
757        final AttributeTypeDefinition attrType = schema.getAttributeType(attrName);
758        if (attrType == null)
759        {
760          return getDefaultSubstringMatchingRule();
761        }
762    
763        final String mrName = attrType.getSubstringMatchingRule(schema);
764        if (mrName != null)
765        {
766          return selectSubstringMatchingRule(mrName);
767        }
768    
769        final String syntaxOID = attrType.getBaseSyntaxOID(schema);
770        if (syntaxOID != null)
771        {
772          return selectMatchingRuleForSyntax(syntaxOID);
773        }
774    
775        return getDefaultSubstringMatchingRule();
776      }
777    
778    
779    
780      /**
781       * Attempts to select the appropriate matching rule to use for substring
782       * matching using the specified matching rule.  If an appropriate matching
783       * rule cannot be determined, then the default substring matching rule will be
784       * selected.
785       *
786       * @param  ruleID  The name or OID of the desired matching rule.
787       *
788       * @return  The selected matching rule.
789       */
790      public static MatchingRule selectSubstringMatchingRule(final String ruleID)
791      {
792        if ((ruleID == null) || (ruleID.length() == 0))
793        {
794          return getDefaultSubstringMatchingRule();
795        }
796    
797        final String lowerName = toLowerCase(ruleID);
798        if (lowerName.equals(
799                 CaseExactStringMatchingRule.LOWER_SUBSTRING_RULE_NAME) ||
800            lowerName.equals(CaseExactStringMatchingRule.SUBSTRING_RULE_OID) ||
801            lowerName.equals("caseexactia5substringsmatch"))
802        {
803          return CaseExactStringMatchingRule.getInstance();
804        }
805        else if (lowerName.equals(
806                      CaseIgnoreListMatchingRule.LOWER_SUBSTRING_RULE_NAME) ||
807                 lowerName.equals(CaseIgnoreListMatchingRule.SUBSTRING_RULE_OID))
808        {
809          return CaseIgnoreListMatchingRule.getInstance();
810        }
811        else if (lowerName.equals(
812                      CaseIgnoreStringMatchingRule.LOWER_SUBSTRING_RULE_NAME) ||
813                 lowerName.equals(
814                      CaseIgnoreStringMatchingRule.SUBSTRING_RULE_OID) ||
815                 lowerName.equals("caseignoreia5substringsmatch") ||
816                 lowerName.equals("1.3.6.1.4.1.1466.109.114.3"))
817        {
818          return CaseIgnoreStringMatchingRule.getInstance();
819        }
820        else if (lowerName.equals(
821                      NumericStringMatchingRule.LOWER_SUBSTRING_RULE_NAME) ||
822                 lowerName.equals(NumericStringMatchingRule.SUBSTRING_RULE_OID))
823        {
824          return NumericStringMatchingRule.getInstance();
825        }
826        else if (lowerName.equals(
827                      OctetStringMatchingRule.LOWER_SUBSTRING_RULE_NAME) ||
828                 lowerName.equals(OctetStringMatchingRule.SUBSTRING_RULE_OID))
829        {
830          return OctetStringMatchingRule.getInstance();
831        }
832        else if (lowerName.equals(
833                      TelephoneNumberMatchingRule.LOWER_SUBSTRING_RULE_NAME) ||
834                 lowerName.equals(TelephoneNumberMatchingRule.SUBSTRING_RULE_OID))
835        {
836          return TelephoneNumberMatchingRule.getInstance();
837        }
838        else
839        {
840          return getDefaultSubstringMatchingRule();
841        }
842      }
843    
844    
845    
846      /**
847       * Retrieves the default matching rule that will be used for substring
848       * matching if no other matching rule is specified or available.  The rule
849       * returned will perform case-ignore string matching.
850       *
851       * @return  The default matching rule that will be used for substring matching
852       *          if no other matching rule is specified or available.
853       */
854      public static MatchingRule getDefaultSubstringMatchingRule()
855      {
856        return CaseIgnoreStringMatchingRule.getInstance();
857      }
858    
859    
860    
861      /**
862       * Attempts to select the appropriate matching rule for use with the syntax
863       * with the specified OID.  If an appropriate matching rule cannot be
864       * determined, then the case-ignore string matching rule will be selected.
865       *
866       * @param  syntaxOID  The OID of the attribute syntax for which to make the
867       *                    determination.
868       *
869       * @return  The selected matching rule.
870       */
871      public static MatchingRule selectMatchingRuleForSyntax(final String syntaxOID)
872      {
873        if (syntaxOID.equals("1.3.6.1.4.1.1466.115.121.1.7"))
874        {
875          return BooleanMatchingRule.getInstance();
876        }
877        else if (syntaxOID.equals("1.3.6.1.4.1.1466.115.121.1.41")) // Postal addr.
878        {
879          return CaseIgnoreListMatchingRule.getInstance();
880        }
881        else if (syntaxOID.equals("1.3.6.1.4.1.1466.115.121.1.12") ||
882             syntaxOID.equals("1.3.6.1.4.1.1466.115.121.1.34")) // name&optional UID
883        {
884          return DistinguishedNameMatchingRule.getInstance();
885        }
886        else if (syntaxOID.equals("1.3.6.1.4.1.1466.115.121.1.24") ||
887             syntaxOID.equals("1.3.6.1.4.1.1466.115.121.1.53")) // UTC time
888        {
889          return GeneralizedTimeMatchingRule.getInstance();
890        }
891        else if (syntaxOID.equals("1.3.6.1.4.1.1466.115.121.1.27"))
892        {
893          return IntegerMatchingRule.getInstance();
894        }
895        else if (syntaxOID.equals("1.3.6.1.4.1.1466.115.121.1.36"))
896        {
897          return NumericStringMatchingRule.getInstance();
898        }
899        else if (syntaxOID.equals("1.3.6.1.4.1.4203.1.1.2") || // auth password
900             syntaxOID.equals("1.3.6.1.4.1.1466.115.121.1.5") || // binary
901             syntaxOID.equals("1.3.6.1.4.1.1466.115.121.1.8") || // certificate
902             syntaxOID.equals("1.3.6.1.4.1.1466.115.121.1.9") || // cert list
903             syntaxOID.equals("1.3.6.1.4.1.1466.115.121.1.10") || // cert pair
904             syntaxOID.equals("1.3.6.1.4.1.1466.115.121.1.28") || // JPEG
905             syntaxOID.equals("1.3.6.1.4.1.1466.115.121.1.40")) // octet string
906        {
907          return OctetStringMatchingRule.getInstance();
908        }
909        else if (syntaxOID.equals("1.3.6.1.4.1.1466.115.121.1.50"))
910        {
911          return TelephoneNumberMatchingRule.getInstance();
912        }
913        else if (syntaxOID.equals("1.3.6.1.4.1.30221.2.3.4")) // JSON object
914        {
915          return getJSONObjectExactMatchingRule();
916        }
917        else
918        {
919          return CaseIgnoreStringMatchingRule.getInstance();
920        }
921      }
922    
923    
924    
925      /**
926       * Retrieves the matching rule that should be used for JSON object exact
927       * matching.  Ideally, we will use the jsonObjectExactMatch matching rule,
928       * but that's only available in the Commercial Edition of the LDAP SDK, so
929       * it's not available in the Standard (or Minimal) Edition.  If we haven't
930       * loaded this matching rule yet, then try to use reflection to get the
931       * jsonObjectExactMatch instance if it's available, and otherwise fall back
932       * to using the caseIgnoreString matching rule.  Either way, cache the result
933       * so that we don't need to use reflection on subsequent calls.
934       *
935       * @return  The matching rule that should be used for JSON object exact
936       *          matching.
937       */
938      private static MatchingRule getJSONObjectExactMatchingRule()
939      {
940        if (jsonObjectExactMatchingRule == null)
941        {
942          try
943          {
944            final Class<?> c = Class.forName("com.unboundid.ldap.sdk.unboundidds." +
945                 "jsonfilter.JSONObjectExactMatchingRule");
946            final Method m = c.getMethod("getInstance");
947            jsonObjectExactMatchingRule = (MatchingRule) m.invoke(null);
948          }
949          catch (final Exception e)
950          {
951            Debug.debugException(e);
952    
953            // We must be using the Standard Edition, which doesn't support the
954            // jsonObjectExactMatch matching rule.  Use the caseIgnoreString
955            // matching rule instead.
956            jsonObjectExactMatchingRule =
957                 CaseIgnoreStringMatchingRule.getInstance();
958          }
959        }
960    
961        return jsonObjectExactMatchingRule;
962      }
963    }