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 */ 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 }