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 }