001/*
002 * Copyright 2008-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2014 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 */
021package com.unboundid.ldap.matchingrules;
022
023
024
025import com.unboundid.asn1.ASN1OctetString;
026import com.unboundid.util.ThreadSafety;
027import com.unboundid.util.ThreadSafetyLevel;
028
029import 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)
039public 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}