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 import com.unboundid.ldap.sdk.LDAPException;
027 import com.unboundid.ldap.sdk.ResultCode;
028 import com.unboundid.util.ThreadSafety;
029 import com.unboundid.util.ThreadSafetyLevel;
030
031 import static com.unboundid.ldap.matchingrules.MatchingRuleMessages.*;
032 import 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)
042 public 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 }