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