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