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