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