001/*
002 * Copyright 2014-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2014-2020 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2014-2020 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.util;
037
038
039
040import java.io.Serializable;
041import java.text.ParseException;
042import java.util.ArrayList;
043import java.util.Collections;
044import java.util.List;
045import java.util.StringTokenizer;
046
047import static com.unboundid.util.UtilityMessages.*;
048
049
050
051/**
052 * This class provides a data structure that may be used for representing object
053 * identifiers.  Since some directory servers support using strings that aren't
054 * valid object identifiers where OIDs are required, this implementation
055 * supports arbitrary strings, but some methods may only be available for valid
056 * OIDs.
057 */
058@NotMutable()
059@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
060public final class OID
061       implements Serializable, Comparable<OID>
062{
063  /**
064   * The serial version UID for this serializable class.
065   */
066  private static final long serialVersionUID = -4542498394670806081L;
067
068
069
070  // The numeric components that comprise this OID.
071  @Nullable private final List<Integer> components;
072
073  // The string representation for this OID.
074  @NotNull private final String oidString;
075
076
077
078  /**
079   * Creates a new OID object from the provided string representation.
080   *
081   * @param  oidString  The string to use to create this OID.
082   */
083  public OID(@Nullable final String oidString)
084  {
085    if (oidString == null)
086    {
087      this.oidString = "";
088    }
089    else
090    {
091      this.oidString = oidString;
092    }
093
094    components = parseComponents(oidString);
095  }
096
097
098
099  /**
100   * Creates a new OID object from the provided set of numeric components.  At
101   * least one component must be provided for a valid OID.
102   *
103   * @param  components  The numeric components to include in the OID.
104   */
105  public OID(@Nullable final int... components)
106  {
107    this(toList(components));
108  }
109
110
111
112  /**
113   * Creates a new OID object from the provided set of numeric components.  At
114   * least one component must be provided for a valid OID.
115   *
116   * @param  components  The numeric components to include in the OID.
117   */
118  public OID(@Nullable final List<Integer> components)
119  {
120    if ((components == null) || components.isEmpty())
121    {
122      this.components = null;
123      oidString = "";
124    }
125    else
126    {
127      this.components =
128           Collections.unmodifiableList(new ArrayList<>(components));
129
130      final StringBuilder buffer = new StringBuilder();
131      for (final Integer i : components)
132      {
133        if (buffer.length() > 0)
134        {
135          buffer.append('.');
136        }
137        buffer.append(i);
138      }
139      oidString = buffer.toString();
140    }
141  }
142
143
144
145  /**
146   * Creates a new OID object with the provided string representation and set
147   * of components.
148   *
149   * @param  oidString   The string representation of this OID.
150   * @param  components  The numeric components for this OID.
151   */
152  private OID(@NotNull final String oidString,
153              @NotNull final List<Integer> components)
154  {
155    this.oidString = oidString;
156    this.components = Collections.unmodifiableList(components);
157  }
158
159
160
161  /**
162   * Retrieves a list corresponding to the elements in the provided array.
163   *
164   * @param  components  The array to convert to a list.
165   *
166   * @return  The list of elements.
167   */
168  @Nullable()
169  private static List<Integer> toList(@Nullable final int... components)
170  {
171    if (components == null)
172    {
173      return null;
174    }
175
176    final ArrayList<Integer> compList = new ArrayList<>(components.length);
177    for (final int i : components)
178    {
179      compList.add(i);
180    }
181    return compList;
182  }
183
184
185
186  /**
187   * Parses the provided string as a numeric OID and extracts the numeric
188   * components from it.
189   *
190   * @param  oidString  The string to parse as a numeric OID.
191   *
192   * @return  The numeric components extracted from the provided string, or
193   *          {@code null} if the provided string does not represent a valid
194   *          numeric OID.
195   */
196  @Nullable()
197  public static List<Integer> parseComponents(@Nullable final String oidString)
198  {
199    if ((oidString == null) || oidString.isEmpty() ||
200        oidString.startsWith(".") || oidString.endsWith(".") ||
201        (oidString.indexOf("..") > 0))
202    {
203      return null;
204    }
205
206    final StringTokenizer tokenizer = new StringTokenizer(oidString, ".");
207    final ArrayList<Integer> compList = new ArrayList<>(10);
208    while (tokenizer.hasMoreTokens())
209    {
210      final String token = tokenizer.nextToken();
211      try
212      {
213        compList.add(Integer.parseInt(token));
214      }
215      catch (final Exception e)
216      {
217        Debug.debugException(e);
218        return null;
219      }
220    }
221
222    return Collections.unmodifiableList(compList);
223  }
224
225
226
227  /**
228   * Parses the provided string as a numeric OID, optionally using additional
229   * strict validation.
230   *
231   * @param  oidString  The string to be parsed as a numeric OID.  It must not
232   *                    be {@code null}.
233   * @param  strict     Indicates whether to use strict validation.  If this is
234   *                    {@code false}, then the method will verify that the
235   *                    provided string is made up of a dotted list of numbers
236   *                    that does not start or end with a period and does not
237   *                    contain consecutive periods.  If this is {@code true},
238   *                    then it will additional verify that the OID contains at
239   *                    least two components, that the value of the first
240   *                    component is not greater than two, and that the value of
241   *                    the second component is not greater than 39 if the value
242   *                    of the first component is zero or one.
243   *
244   * @return  The OID that was parsed from the provided string.
245   *
246   * @throws  ParseException  If the provided string cannot be parsed as a valid
247   *                          numeric OID.
248   */
249  @NotNull()
250  public static OID parseNumericOID(@Nullable final String oidString,
251                                    final boolean strict)
252         throws ParseException
253  {
254    if ((oidString == null) || oidString.isEmpty())
255    {
256      throw new ParseException(ERR_OID_EMPTY.get(), 0);
257    }
258
259    int componentStartPos = 0;
260    final List<Integer> components = new ArrayList<>(oidString.length());
261    final StringBuilder buffer = new StringBuilder(oidString.length());
262    for (int i=0; i < oidString.length(); i++)
263    {
264      final char c = oidString.charAt(i);
265      switch (c)
266      {
267        case '0':
268        case '1':
269        case '2':
270        case '3':
271        case '4':
272        case '5':
273        case '6':
274        case '7':
275        case '8':
276        case '9':
277          buffer.append(c);
278          break;
279
280        case '.':
281          if (buffer.length() == 0)
282          {
283            if (i == 0)
284            {
285              throw new ParseException(
286                   ERR_OID_STARTS_WITH_PERIOD.get(oidString), i);
287            }
288            else
289            {
290              throw new ParseException(
291                   ERR_OID_CONSECUTIVE_PERIODS.get(oidString, i), i);
292            }
293          }
294
295          if ((buffer.length() > 1) && (buffer.charAt(0) == '0'))
296          {
297            throw new ParseException(
298                 ERR_OID_LEADING_ZERO.get(oidString, buffer.toString()),
299                 componentStartPos);
300          }
301
302          try
303          {
304            components.add(Integer.parseInt(buffer.toString()));
305          }
306          catch (final Exception e)
307          {
308            Debug.debugException(e);
309            throw new ParseException(
310                 ERR_OID_CANNOT_PARSE_AS_INT.get( oidString, buffer.toString(),
311                      componentStartPos),
312                 componentStartPos);
313          }
314          buffer.setLength(0);
315          componentStartPos = (i + 1);
316          break;
317
318        default:
319          throw new ParseException(
320               ERR_OID_ILLEGAL_CHARACTER.get(oidString, c, i), i);
321      }
322    }
323
324    if (buffer.length() == 0)
325    {
326      throw new ParseException(
327           ERR_OID_ENDS_WITH_PERIOD.get(oidString), (oidString.length() - 1));
328    }
329
330    if ((buffer.length() > 1) && (buffer.charAt(0) == '0'))
331    {
332      throw new ParseException(
333           ERR_OID_LEADING_ZERO.get(oidString, buffer.toString()),
334           componentStartPos);
335    }
336
337    try
338    {
339      components.add(Integer.parseInt(buffer.toString()));
340    }
341    catch (final Exception e)
342    {
343      Debug.debugException(e);
344      throw new ParseException(
345           ERR_OID_CANNOT_PARSE_AS_INT.get( oidString, buffer.toString(),
346                componentStartPos),
347           componentStartPos);
348    }
349
350
351    if (strict)
352    {
353      if (components.size() < 2)
354      {
355        throw new ParseException(
356             ERR_OID_NOT_ENOUGH_COMPONENTS.get(oidString), 0);
357      }
358
359      final int firstComponent = components.get(0);
360      final int secondComponent = components.get(1);
361      switch (firstComponent)
362      {
363        case 0:
364        case 1:
365          if (secondComponent > 39)
366          {
367            throw new ParseException(
368                 ERR_OID_ILLEGAL_SECOND_COMPONENT.get(oidString,
369                      secondComponent, firstComponent),
370                 0);
371          }
372          break;
373
374        case 2:
375          // We don't need to do any more validation.
376          break;
377
378        default:
379          // Invalid value for the first component.
380          throw new ParseException(
381               ERR_OID_ILLEGAL_FIRST_COMPONENT.get(oidString, firstComponent),
382               0);
383      }
384    }
385
386    return new OID(oidString, components);
387  }
388
389
390
391  /**
392   * Indicates whether the provided string represents a valid numeric OID.  Note
393   * this this method only ensures that the value is made up of a dotted list of
394   * numbers that does not start or end with a period and does not contain two
395   * consecutive periods.  The {@link #isStrictlyValidNumericOID(String)} method
396   * performs additional validation, including ensuring that the OID contains
397   * at least two components, that the value of the first component is not
398   * greater than two, and that the value of the second component is not greater
399   * than 39 if the value of the first component is zero or one.
400   *
401   * @param  s  The string for which to make the determination.
402   *
403   * @return  {@code true} if the provided string represents a valid numeric
404   *          OID, or {@code false} if not.
405   */
406  public static boolean isValidNumericOID(@Nullable final String s)
407  {
408    return new OID(s).isValidNumericOID();
409  }
410
411
412
413  /**
414   * Indicates whether the provided string represents a valid numeric OID.  Note
415   * this this method only ensures that the value is made up of a dotted list of
416   * numbers that does not start or end with a period and does not contain two
417   * consecutive periods.  The {@link #isStrictlyValidNumericOID()} method
418   * performs additional validation, including ensuring that the OID contains
419   * at least two components, that the value of the first component is not
420   * greater than two, and that the value of the second component is not greater
421   * than 39 if the value of the first component is zero or one.
422   *
423   * @return  {@code true} if this object represents a valid numeric OID, or
424   *          {@code false} if not.
425   */
426  public boolean isValidNumericOID()
427  {
428    return (components != null);
429  }
430
431
432
433  /**
434   * Indicates whether this object represents a strictly valid numeric OID.
435   * In addition to ensuring that the value is made up of a dotted list of
436   * numbers that does not start or end with a period or contain two consecutive
437   * periods, this method also ensures that the OID contains at least two
438   * components, that the value of the first component is not greater than two,
439   * and that the value of the second component is not greater than 39 if the
440   * value of the first component is zero or one.
441   *
442   * @param  s  The string for which to make the determination.
443   *
444   * @return  {@code true} if this object represents a strictly valid numeric
445   *          OID, or {@code false} if not.
446   */
447  public static boolean isStrictlyValidNumericOID(@Nullable final String s)
448  {
449    return new OID(s).isStrictlyValidNumericOID();
450  }
451
452
453
454  /**
455   * Indicates whether this object represents a strictly valid numeric OID.
456   * In addition to ensuring that the value is made up of a dotted list of
457   * numbers that does not start or end with a period or contain two consecutive
458   * periods, this method also ensures that the OID contains at least two
459   * components, that the value of the first component is not greater than two,
460   * and that the value of the second component is not greater than 39 if the
461   * value of the first component is zero or one.
462   *
463   * @return  {@code true} if this object represents a strictly valid numeric
464   *          OID, or {@code false} if not.
465   */
466  public boolean isStrictlyValidNumericOID()
467  {
468    if ((components == null) || (components.size() < 2))
469    {
470      return false;
471    }
472
473    final int firstComponent = components.get(0);
474    final int secondComponent = components.get(1);
475    switch (firstComponent)
476    {
477      case 0:
478      case 1:
479        // The value of the second component must not be greater than 39.
480        return (secondComponent <= 39);
481
482      case 2:
483        // We don't need to do any more validation.
484        return true;
485
486      default:
487        // Invalid value for the first component.
488        return false;
489    }
490  }
491
492
493
494  /**
495   * Retrieves the numeric components that comprise this OID.  This will only
496   * return a non-{@code null} value if {@link #isValidNumericOID} returns
497   * {@code true}.
498   *
499   * @return  The numeric components that comprise this OID, or {@code null} if
500   *          this object does not represent a valid numeric OID.
501   */
502  @Nullable()
503  public List<Integer> getComponents()
504  {
505    return components;
506  }
507
508
509
510  /**
511   * Retrieves a hash code for this OID.
512   *
513   * @return  A hash code for this OID.
514   */
515  @Override()
516  public int hashCode()
517  {
518    if (components == null)
519    {
520      return oidString.hashCode();
521    }
522    else
523    {
524      int hashCode = 0;
525      for (final int i : components)
526      {
527        hashCode += i;
528      }
529      return hashCode;
530    }
531  }
532
533
534
535  /**
536   * Indicates whether the provided object is equal to this OID.
537   *
538   * @param  o  The object for which to make the determination.
539   *
540   * @return  {@code true} if the provided object is equal to this OID, or
541   *          {@code false} if not.
542   */
543  @Override()
544  public boolean equals(@Nullable final Object o)
545  {
546    if (o == null)
547    {
548      return false;
549    }
550
551    if (o == this)
552    {
553      return true;
554    }
555
556    if (o instanceof OID)
557    {
558      final OID oid = (OID) o;
559      if (components == null)
560      {
561        return oidString.equals(oid.oidString);
562      }
563      else
564      {
565        return components.equals(oid.components);
566      }
567    }
568
569    return false;
570  }
571
572
573
574  /**
575   * Indicates the position of the provided object relative to this OID in a
576   * sorted list.
577   *
578   * @param  oid  The OID to compare against this OID.
579   *
580   * @return  A negative value if this OID should come before the provided OID
581   *          in a sorted list, a positive value if this OID should come after
582   *          the provided OID in a sorted list, or zero if the two OIDs
583   *          represent equivalent values.
584   */
585  @Override()
586  public int compareTo(@NotNull final OID oid)
587  {
588    if (components == null)
589    {
590      if (oid.components == null)
591      {
592        // Neither is a valid numeric OID, so we'll just compare the string
593        // representations.
594        return oidString.compareTo(oid.oidString);
595      }
596      else
597      {
598        // A valid numeric OID will always come before a non-valid one.
599        return 1;
600      }
601    }
602
603    if (oid.components == null)
604    {
605      // A valid numeric OID will always come before a non-valid one.
606      return -1;
607    }
608
609    for (int i=0; i < Math.min(components.size(), oid.components.size()); i++)
610    {
611      final int thisValue = components.get(i);
612      final int thatValue = oid.components.get(i);
613
614      if (thisValue < thatValue)
615      {
616        // This OID has a lower number in the first non-equal slot than the
617        // provided OID.
618        return -1;
619      }
620      else if (thisValue > thatValue)
621      {
622        // This OID has a higher number in the first non-equal slot than the
623        // provided OID.
624        return 1;
625      }
626    }
627
628    // Where the values overlap, they are equivalent.  Make the determination
629    // based on which is longer.
630    if (components.size() < oid.components.size())
631    {
632      // The provided OID is longer than this OID.
633      return -1;
634    }
635    else if (components.size() > oid.components.size())
636    {
637      // The provided OID is shorter than this OID.
638      return 1;
639    }
640    else
641    {
642      // They represent equivalent OIDs.
643      return 0;
644    }
645  }
646
647
648
649  /**
650   * Retrieves a string representation of this OID.
651   *
652   * @return  A string representation of this OID.
653   */
654  @Override()
655  @NotNull()
656  public String toString()
657  {
658    return oidString;
659  }
660}