001    /*
002     * Copyright 2008-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 com.unboundid.asn1.ASN1OctetString;
026    
027    import static com.unboundid.util.StaticUtils.*;
028    
029    
030    
031    /**
032     * This class provides an implementation of a matching rule that uses
033     * case-sensitive matching that also treats multiple consecutive (non-escaped)
034     * spaces as a single space.
035     */
036    public final class CaseExactStringMatchingRule
037           extends AcceptAllSimpleMatchingRule
038    {
039      /**
040       * The singleton instance that will be returned from the {@code getInstance}
041       * method.
042       */
043      private static final CaseExactStringMatchingRule INSTANCE =
044           new CaseExactStringMatchingRule();
045    
046    
047    
048      /**
049       * The name for the caseExactMatch equality matching rule.
050       */
051      public static final String EQUALITY_RULE_NAME = "caseExactMatch";
052    
053    
054    
055      /**
056       * The name for the caseExactMatch equality matching rule, formatted in all
057       * lowercase characters.
058       */
059      static final String LOWER_EQUALITY_RULE_NAME =
060           toLowerCase(EQUALITY_RULE_NAME);
061    
062    
063    
064      /**
065       * The OID for the caseExactMatch equality matching rule.
066       */
067      public static final String EQUALITY_RULE_OID = "2.5.13.5";
068    
069    
070    
071      /**
072       * The name for the caseExactOrderingMatch ordering matching rule.
073       */
074      public static final String ORDERING_RULE_NAME = "caseExactOrderingMatch";
075    
076    
077    
078      /**
079       * The name for the caseExactOrderingMatch ordering matching rule, formatted
080       * in all lowercase characters.
081       */
082      static final String LOWER_ORDERING_RULE_NAME =
083           toLowerCase(ORDERING_RULE_NAME);
084    
085    
086    
087      /**
088       * The OID for the caseExactOrderingMatch ordering matching rule.
089       */
090      public static final String ORDERING_RULE_OID = "2.5.13.6";
091    
092    
093    
094      /**
095       * The name for the caseExactSubstringsMatch substring matching rule.
096       */
097      public static final String SUBSTRING_RULE_NAME = "caseExactSubstringsMatch";
098    
099    
100    
101      /**
102       * The name for the caseExactSubstringsMatch substring matching rule,
103       * formatted in all lowercase characters.
104       */
105      static final String LOWER_SUBSTRING_RULE_NAME =
106           toLowerCase(SUBSTRING_RULE_NAME);
107    
108    
109    
110      /**
111       * The OID for the caseExactSubstringsMatch substring matching rule.
112       */
113      public static final String SUBSTRING_RULE_OID = "2.5.13.7";
114    
115    
116    
117      /**
118       * The serial version UID for this serializable class.
119       */
120      private static final long serialVersionUID = -6336492464430413364L;
121    
122    
123    
124      /**
125       * Creates a new instance of this case exact string matching rule.
126       */
127      public CaseExactStringMatchingRule()
128      {
129        // No implementation is required.
130      }
131    
132    
133    
134      /**
135       * Retrieves a singleton instance of this matching rule.
136       *
137       * @return  A singleton instance of this matching rule.
138       */
139      public static CaseExactStringMatchingRule getInstance()
140      {
141        return INSTANCE;
142      }
143    
144    
145    
146      /**
147       * {@inheritDoc}
148       */
149      @Override()
150      public String getEqualityMatchingRuleName()
151      {
152        return EQUALITY_RULE_NAME;
153      }
154    
155    
156    
157      /**
158       * {@inheritDoc}
159       */
160      @Override()
161      public String getEqualityMatchingRuleOID()
162      {
163        return EQUALITY_RULE_OID;
164      }
165    
166    
167    
168      /**
169       * {@inheritDoc}
170       */
171      @Override()
172      public String getOrderingMatchingRuleName()
173      {
174        return ORDERING_RULE_NAME;
175      }
176    
177    
178    
179      /**
180       * {@inheritDoc}
181       */
182      @Override()
183      public String getOrderingMatchingRuleOID()
184      {
185        return ORDERING_RULE_OID;
186      }
187    
188    
189    
190      /**
191       * {@inheritDoc}
192       */
193      @Override()
194      public String getSubstringMatchingRuleName()
195      {
196        return SUBSTRING_RULE_NAME;
197      }
198    
199    
200    
201      /**
202       * {@inheritDoc}
203       */
204      @Override()
205      public String getSubstringMatchingRuleOID()
206      {
207        return SUBSTRING_RULE_OID;
208      }
209    
210    
211    
212      /**
213       * {@inheritDoc}
214       */
215      @Override()
216      public boolean valuesMatch(final ASN1OctetString value1,
217                                 final ASN1OctetString value2)
218      {
219        // Try to use a quick, no-copy determination if possible.  If this fails,
220        // then we'll fall back on a more thorough, but more costly, approach.
221        final byte[] value1Bytes = value1.getValue();
222        final byte[] value2Bytes = value2.getValue();
223        if (value1Bytes.length == value2Bytes.length)
224        {
225          for (int i=0; i< value1Bytes.length; i++)
226          {
227            final byte b1 = value1Bytes[i];
228            final byte b2 = value2Bytes[i];
229    
230            if (((b1 & 0x7F) != (b1 & 0xFF)) ||
231                ((b2 & 0x7F) != (b2 & 0xFF)))
232            {
233              return normalize(value1).equals(normalize(value2));
234            }
235            else if (b1 != b2)
236            {
237              if ((b1 == ' ') || (b2 == ' '))
238              {
239                return normalize(value1).equals(normalize(value2));
240              }
241              else
242              {
243                return false;
244              }
245            }
246          }
247    
248          // If we've gotten to this point, then the values must be equal.
249          return true;
250        }
251        else
252        {
253          return normalizeInternal(value1, false, (byte) 0x00).equals(
254                      normalizeInternal(value2, false, (byte) 0x00));
255        }
256      }
257    
258    
259    
260      /**
261       * {@inheritDoc}
262       */
263      @Override()
264      public ASN1OctetString normalize(final ASN1OctetString value)
265      {
266        return normalizeInternal(value, false, (byte) 0x00);
267      }
268    
269    
270    
271      /**
272       * {@inheritDoc}
273       */
274      @Override()
275      public ASN1OctetString normalizeSubstring(final ASN1OctetString value,
276                                                final byte substringType)
277      {
278        return normalizeInternal(value, true, substringType);
279      }
280    
281    
282    
283      /**
284       * Normalizes the provided value for use in either an equality or substring
285       * matching operation.
286       *
287       * @param  value          The value to be normalized.
288       * @param  isSubstring    Indicates whether the value should be normalized as
289       *                        part of a substring assertion rather than an
290       *                        equality assertion.
291       * @param  substringType  The substring type for the element, if it is to be
292       *                        part of a substring assertion.
293       *
294       * @return  The appropriately normalized form of the provided value.
295       */
296      private static ASN1OctetString normalizeInternal(final ASN1OctetString value,
297                                                       final boolean isSubstring,
298                                                       final byte substringType)
299      {
300        final byte[] valueBytes = value.getValue();
301        if (valueBytes.length == 0)
302        {
303          return value;
304        }
305    
306        final boolean trimInitial;
307        final boolean trimFinal;
308        if (isSubstring)
309        {
310          switch (substringType)
311          {
312            case SUBSTRING_TYPE_SUBINITIAL:
313              trimInitial = true;
314              trimFinal   = false;
315              break;
316    
317            case SUBSTRING_TYPE_SUBFINAL:
318              trimInitial = false;
319              trimFinal   = true;
320              break;
321    
322            default:
323              trimInitial = false;
324              trimFinal   = false;
325              break;
326          }
327        }
328        else
329        {
330          trimInitial = true;
331          trimFinal   = true;
332        }
333    
334        // Count the number of duplicate spaces in the value, and determine whether
335        // there are any non-space characters.  Also, see if there are any non-ASCII
336        // characters.
337        boolean containsNonSpace = false;
338        boolean lastWasSpace = trimInitial;
339        int numDuplicates = 0;
340        for (final byte b : valueBytes)
341        {
342          if ((b & 0x7F) != (b & 0xFF))
343          {
344            return normalizeNonASCII(value, trimInitial, trimFinal);
345          }
346    
347          if (b == ' ')
348          {
349            if (lastWasSpace)
350            {
351              numDuplicates++;
352            }
353            else
354            {
355              lastWasSpace = true;
356            }
357          }
358          else
359          {
360            containsNonSpace = true;
361            lastWasSpace = false;
362          }
363        }
364    
365        if (! containsNonSpace)
366        {
367          return new ASN1OctetString(" ");
368        }
369    
370        if (lastWasSpace && trimFinal)
371        {
372          numDuplicates++;
373        }
374    
375    
376        // Create a new byte array to hold the normalized value.
377        lastWasSpace = trimInitial;
378        int targetPos = 0;
379        final byte[] normalizedBytes = new byte[valueBytes.length - numDuplicates];
380        for (int i=0; i < valueBytes.length; i++)
381        {
382          if (valueBytes[i] == ' ')
383          {
384            if (lastWasSpace || (trimFinal && (i == (valueBytes.length - 1))))
385            {
386              // No action is required.
387            }
388            else
389            {
390              // This condition is needed to handle the special case in which
391              // there are multiple spaces at the end of the value.
392              if (targetPos < normalizedBytes.length)
393              {
394                normalizedBytes[targetPos++] = ' ';
395                lastWasSpace = true;
396              }
397            }
398          }
399          else
400          {
401            normalizedBytes[targetPos++] = valueBytes[i];
402            lastWasSpace = false;
403          }
404        }
405    
406    
407        return new ASN1OctetString(normalizedBytes);
408      }
409    
410    
411    
412      /**
413       * Normalizes the provided value a string representation, properly handling
414       * any non-ASCII characters.
415       *
416       * @param  value        The value to be normalized.
417       * @param  trimInitial  Indicates whether to trim off all leading spaces at
418       *                      the beginning of the value.
419       * @param  trimFinal    Indicates whether to trim off all trailing spaces at
420       *                      the end of the value.
421       *
422       * @return  The normalized form of the value.
423       */
424      private static ASN1OctetString normalizeNonASCII(final ASN1OctetString value,
425                                                       final boolean trimInitial,
426                                                       final boolean trimFinal)
427      {
428        final StringBuilder buffer = new StringBuilder(value.stringValue());
429    
430        int pos = 0;
431        boolean lastWasSpace = trimInitial;
432        while (pos < buffer.length())
433        {
434          final char c = buffer.charAt(pos++);
435          if (c == ' ')
436          {
437            if (lastWasSpace || (trimFinal && (pos >= buffer.length())))
438            {
439              buffer.deleteCharAt(--pos);
440            }
441            else
442            {
443              lastWasSpace = true;
444            }
445          }
446          else
447          {
448            lastWasSpace = false;
449          }
450        }
451    
452        // It is possible that there could be an extra space at the end.  If that's
453        // the case, then remove it.
454        if (trimFinal && (buffer.length() > 0) &&
455            (buffer.charAt(buffer.length() - 1) == ' '))
456        {
457          buffer.deleteCharAt(buffer.length() - 1);
458        }
459    
460        return new ASN1OctetString(buffer.toString());
461      }
462    }