001    /*
002     * Copyright 2008-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 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    }