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