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 }