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}