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