001/*
002 * Copyright 2008-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2019 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}