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