001/* 002 * Copyright 2008-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2017 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}