001/* 002 * Copyright 2008-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2018 Ping Identity Corporation 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.ldap.sdk.LDAPException; 027import com.unboundid.ldap.sdk.ResultCode; 028import com.unboundid.util.Debug; 029import com.unboundid.util.StaticUtils; 030import com.unboundid.util.ThreadSafety; 031import com.unboundid.util.ThreadSafetyLevel; 032 033import static com.unboundid.ldap.matchingrules.MatchingRuleMessages.*; 034 035 036 037/** 038 * This class provides an implementation of a matching rule that performs 039 * equality and ordering comparisons against values that should be integers. 040 * Substring matching is not supported. 041 */ 042@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 043public final class IntegerMatchingRule 044 extends MatchingRule 045{ 046 /** 047 * The singleton instance that will be returned from the {@code getInstance} 048 * method. 049 */ 050 private static final IntegerMatchingRule INSTANCE = 051 new IntegerMatchingRule(); 052 053 054 055 /** 056 * The name for the integerMatch equality matching rule. 057 */ 058 public static final String EQUALITY_RULE_NAME = "integerMatch"; 059 060 061 062 /** 063 * The name for the integerMatch equality matching rule, formatted in all 064 * lowercase characters. 065 */ 066 static final String LOWER_EQUALITY_RULE_NAME = 067 StaticUtils.toLowerCase(EQUALITY_RULE_NAME); 068 069 070 071 /** 072 * The OID for the integerMatch equality matching rule. 073 */ 074 public static final String EQUALITY_RULE_OID = "2.5.13.14"; 075 076 077 078 /** 079 * The name for the integerOrderingMatch ordering matching rule. 080 */ 081 public static final String ORDERING_RULE_NAME = "integerOrderingMatch"; 082 083 084 085 /** 086 * The name for the integerOrderingMatch ordering matching rule, formatted 087 * in all lowercase characters. 088 */ 089 static final String LOWER_ORDERING_RULE_NAME = 090 StaticUtils.toLowerCase(ORDERING_RULE_NAME); 091 092 093 094 /** 095 * The OID for the integerOrderingMatch ordering matching rule. 096 */ 097 public static final String ORDERING_RULE_OID = "2.5.13.15"; 098 099 100 101 /** 102 * The serial version UID for this serializable class. 103 */ 104 private static final long serialVersionUID = -9056942146971528818L; 105 106 107 108 /** 109 * Creates a new instance of this integer matching rule. 110 */ 111 public IntegerMatchingRule() 112 { 113 // No implementation is required. 114 } 115 116 117 118 /** 119 * Retrieves a singleton instance of this matching rule. 120 * 121 * @return A singleton instance of this matching rule. 122 */ 123 public static IntegerMatchingRule getInstance() 124 { 125 return INSTANCE; 126 } 127 128 129 130 /** 131 * {@inheritDoc} 132 */ 133 @Override() 134 public String getEqualityMatchingRuleName() 135 { 136 return EQUALITY_RULE_NAME; 137 } 138 139 140 141 /** 142 * {@inheritDoc} 143 */ 144 @Override() 145 public String getEqualityMatchingRuleOID() 146 { 147 return EQUALITY_RULE_OID; 148 } 149 150 151 152 /** 153 * {@inheritDoc} 154 */ 155 @Override() 156 public String getOrderingMatchingRuleName() 157 { 158 return ORDERING_RULE_NAME; 159 } 160 161 162 163 /** 164 * {@inheritDoc} 165 */ 166 @Override() 167 public String getOrderingMatchingRuleOID() 168 { 169 return ORDERING_RULE_OID; 170 } 171 172 173 174 /** 175 * {@inheritDoc} 176 */ 177 @Override() 178 public String getSubstringMatchingRuleName() 179 { 180 return null; 181 } 182 183 184 185 /** 186 * {@inheritDoc} 187 */ 188 @Override() 189 public String getSubstringMatchingRuleOID() 190 { 191 return null; 192 } 193 194 195 196 /** 197 * {@inheritDoc} 198 */ 199 @Override() 200 public boolean valuesMatch(final ASN1OctetString value1, 201 final ASN1OctetString value2) 202 throws LDAPException 203 { 204 return normalize(value1).equals(normalize(value2)); 205 } 206 207 208 209 /** 210 * {@inheritDoc} 211 */ 212 @Override() 213 public boolean matchesAnyValue(final ASN1OctetString assertionValue, 214 final ASN1OctetString[] attributeValues) 215 throws LDAPException 216 { 217 if ((assertionValue == null) || (attributeValues == null) || 218 (attributeValues.length == 0)) 219 { 220 return false; 221 } 222 223 final ASN1OctetString normalizedAssertionValue = normalize(assertionValue); 224 225 for (final ASN1OctetString attributeValue : attributeValues) 226 { 227 try 228 { 229 if (normalizedAssertionValue.equalsIgnoreType( 230 normalize(attributeValue))) 231 { 232 return true; 233 } 234 } 235 catch (final Exception e) 236 { 237 Debug.debugException(e); 238 } 239 } 240 241 return false; 242 } 243 244 245 246 /** 247 * {@inheritDoc} 248 */ 249 @Override() 250 public boolean matchesSubstring(final ASN1OctetString value, 251 final ASN1OctetString subInitial, 252 final ASN1OctetString[] subAny, 253 final ASN1OctetString subFinal) 254 throws LDAPException 255 { 256 throw new LDAPException(ResultCode.INAPPROPRIATE_MATCHING, 257 ERR_INTEGER_SUBSTRING_MATCHING_NOT_SUPPORTED.get()); 258 } 259 260 261 262 /** 263 * {@inheritDoc} 264 */ 265 @Override() 266 public int compareValues(final ASN1OctetString value1, 267 final ASN1OctetString value2) 268 throws LDAPException 269 { 270 final byte[] norm1Bytes = normalize(value1).getValue(); 271 final byte[] norm2Bytes = normalize(value2).getValue(); 272 273 if (norm1Bytes[0] == '-') 274 { 275 if (norm2Bytes[0] == '-') 276 { 277 // Both values are negative. The smaller negative is the larger value. 278 if (norm1Bytes.length < norm2Bytes.length) 279 { 280 return 1; 281 } 282 else if (norm1Bytes.length > norm2Bytes.length) 283 { 284 return -1; 285 } 286 else 287 { 288 for (int i=1; i < norm1Bytes.length; i++) 289 { 290 final int difference = norm2Bytes[i] - norm1Bytes[i]; 291 if (difference != 0) 292 { 293 return difference; 294 } 295 } 296 297 return 0; 298 } 299 } 300 else 301 { 302 // The first is negative and the second is positive. 303 return -1; 304 } 305 } 306 else 307 { 308 if (norm2Bytes[0] == '-') 309 { 310 // The first is positive and the second is negative. 311 return 1; 312 } 313 else 314 { 315 // Both values are positive. 316 if (norm1Bytes.length < norm2Bytes.length) 317 { 318 return -1; 319 } 320 else if (norm1Bytes.length > norm2Bytes.length) 321 { 322 return 1; 323 } 324 else 325 { 326 for (int i=0; i < norm1Bytes.length; i++) 327 { 328 final int difference = norm1Bytes[i] - norm2Bytes[i]; 329 if (difference != 0) 330 { 331 return difference; 332 } 333 } 334 335 return 0; 336 } 337 } 338 } 339 } 340 341 342 343 /** 344 * {@inheritDoc} 345 */ 346 @Override() 347 public ASN1OctetString normalize(final ASN1OctetString value) 348 throws LDAPException 349 { 350 // It is likely that the provided value is already acceptable, so we should 351 // try to validate it without any unnecessary allocation. 352 final byte[] valueBytes = value.getValue(); 353 if (valueBytes.length == 0) 354 { 355 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 356 ERR_INTEGER_ZERO_LENGTH_NOT_ALLOWED.get()); 357 } 358 359 if ((valueBytes[0] == ' ') || (valueBytes[valueBytes.length-1] == ' ')) 360 { 361 // There is either a leading or trailing space, which needs to be 362 // stripped out so we'll have to allocate memory for this. 363 final String valueStr = value.stringValue().trim(); 364 if (valueStr.isEmpty()) 365 { 366 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 367 ERR_INTEGER_ZERO_LENGTH_NOT_ALLOWED.get()); 368 } 369 370 for (int i=0; i < valueStr.length(); i++) 371 { 372 switch (valueStr.charAt(i)) 373 { 374 case '-': 375 // This is only acceptable as the first character, and only if it is 376 // followed by one or more other characters. 377 if ((i != 0) || (valueStr.length() == 1)) 378 { 379 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 380 ERR_INTEGER_INVALID_CHARACTER.get(i)); 381 } 382 break; 383 384 case '0': 385 // This is acceptable anywhere except the as first character unless 386 // it is the only character, or as the second character if the first 387 // character is a dash. 388 if (((i == 0) && (valueStr.length() > 1)) || 389 ((i == 1) && (valueStr.charAt(0) == '-'))) 390 { 391 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 392 ERR_INTEGER_INVALID_LEADING_ZERO.get()); 393 } 394 break; 395 396 case '1': 397 case '2': 398 case '3': 399 case '4': 400 case '5': 401 case '6': 402 case '7': 403 case '8': 404 case '9': 405 // These are always acceptable. 406 break; 407 408 default: 409 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 410 ERR_INTEGER_INVALID_CHARACTER.get(i)); 411 } 412 } 413 414 return new ASN1OctetString(valueStr); 415 } 416 417 418 // Perform the validation against the contents of the byte array. 419 for (int i=0; i < valueBytes.length; i++) 420 { 421 switch (valueBytes[i]) 422 { 423 case '-': 424 // This is only acceptable as the first character, and only if it is 425 // followed by one or more other characters. 426 if ((i != 0) || (valueBytes.length == 1)) 427 { 428 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 429 ERR_INTEGER_INVALID_CHARACTER.get(i)); 430 } 431 break; 432 433 case '0': 434 // This is acceptable anywhere except the as first character unless 435 // it is the only character, or as the second character if the first 436 // character is a dash. 437 if (((i == 0) && (valueBytes.length > 1)) || 438 ((i == 1) && (valueBytes[0] == '-'))) 439 { 440 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 441 ERR_INTEGER_INVALID_LEADING_ZERO.get()); 442 } 443 break; 444 445 case '1': 446 case '2': 447 case '3': 448 case '4': 449 case '5': 450 case '6': 451 case '7': 452 case '8': 453 case '9': 454 // These are always acceptable. 455 break; 456 457 default: 458 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 459 ERR_INTEGER_INVALID_CHARACTER.get(i)); 460 } 461 } 462 463 return value; 464 } 465 466 467 468 /** 469 * {@inheritDoc} 470 */ 471 @Override() 472 public ASN1OctetString normalizeSubstring(final ASN1OctetString value, 473 final byte substringType) 474 throws LDAPException 475 { 476 throw new LDAPException(ResultCode.INAPPROPRIATE_MATCHING, 477 ERR_INTEGER_SUBSTRING_MATCHING_NOT_SUPPORTED.get()); 478 } 479}