001/*
002 * Copyright 2014-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2014-2024 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-2024 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 that is a child of the provided parent OID.
147   *
148   * @param  parentOID       The parent OID below which the child should be
149   *                         created.  It must not be {@code null}, and it must
150   *                         be a valid numeric OID.
151   * @param  childComponent  The integer value for the child component.
152   *
153   * @throws  ParseException  If the provided parent OID is not a valid numeric
154   *                          OID.
155   */
156  public OID(@NotNull final OID parentOID, final int childComponent)
157         throws ParseException
158  {
159    if (parentOID.components == null)
160    {
161      throw new ParseException(
162           ERR_OID_INIT_PARENT_NOT_VALID.get(String.valueOf(parentOID)), 0);
163    }
164
165    components = new ArrayList<>(parentOID.components.size() + 1);
166    components.addAll(parentOID.components);
167    components.add(childComponent);
168
169    oidString = parentOID.oidString + '.' + childComponent;
170  }
171
172
173
174  /**
175   * Creates a new OID object with the provided string representation and set
176   * of components.
177   *
178   * @param  oidString   The string representation of this OID.
179   * @param  components  The numeric components for this OID.
180   */
181  private OID(@NotNull final String oidString,
182              @NotNull final List<Integer> components)
183  {
184    this.oidString = oidString;
185    this.components = Collections.unmodifiableList(components);
186  }
187
188
189
190  /**
191   * Retrieves a list corresponding to the elements in the provided array.
192   *
193   * @param  components  The array to convert to a list.
194   *
195   * @return  The list of elements.
196   */
197  @Nullable()
198  private static List<Integer> toList(@Nullable final int... components)
199  {
200    if (components == null)
201    {
202      return null;
203    }
204
205    final ArrayList<Integer> compList = new ArrayList<>(components.length);
206    for (final int i : components)
207    {
208      compList.add(i);
209    }
210    return compList;
211  }
212
213
214
215  /**
216   * Parses the provided string as a numeric OID and extracts the numeric
217   * components from it.
218   *
219   * @param  oidString  The string to parse as a numeric OID.
220   *
221   * @return  The numeric components extracted from the provided string, or
222   *          {@code null} if the provided string does not represent a valid
223   *          numeric OID.
224   */
225  @Nullable()
226  public static List<Integer> parseComponents(@Nullable final String oidString)
227  {
228    if ((oidString == null) || oidString.isEmpty() ||
229        oidString.startsWith(".") || oidString.endsWith(".") ||
230        (oidString.indexOf("..") > 0))
231    {
232      return null;
233    }
234
235    final StringTokenizer tokenizer = new StringTokenizer(oidString, ".");
236    final ArrayList<Integer> compList = new ArrayList<>(10);
237    while (tokenizer.hasMoreTokens())
238    {
239      final String token = tokenizer.nextToken();
240      try
241      {
242        compList.add(Integer.parseInt(token));
243      }
244      catch (final Exception e)
245      {
246        Debug.debugException(e);
247        return null;
248      }
249    }
250
251    return Collections.unmodifiableList(compList);
252  }
253
254
255
256  /**
257   * Parses the provided string as a numeric OID, optionally using additional
258   * strict validation.
259   *
260   * @param  oidString  The string to be parsed as a numeric OID.  It must not
261   *                    be {@code null}.
262   * @param  strict     Indicates whether to use strict validation.  If this is
263   *                    {@code false}, then the method will verify that the
264   *                    provided string is made up of a dotted list of numbers
265   *                    that does not start or end with a period and does not
266   *                    contain consecutive periods.  If this is {@code true},
267   *                    then it will additional verify that the OID contains at
268   *                    least two components, that the value of the first
269   *                    component is not greater than two, and that the value of
270   *                    the second component is not greater than 39 if the value
271   *                    of the first component is zero or one.
272   *
273   * @return  The OID that was parsed from the provided string.
274   *
275   * @throws  ParseException  If the provided string cannot be parsed as a valid
276   *                          numeric OID.
277   */
278  @NotNull()
279  public static OID parseNumericOID(@Nullable final String oidString,
280                                    final boolean strict)
281         throws ParseException
282  {
283    if ((oidString == null) || oidString.isEmpty())
284    {
285      throw new ParseException(ERR_OID_EMPTY.get(), 0);
286    }
287
288    int componentStartPos = 0;
289    final List<Integer> components = new ArrayList<>(oidString.length());
290    final StringBuilder buffer = new StringBuilder(oidString.length());
291    for (int i=0; i < oidString.length(); i++)
292    {
293      final char c = oidString.charAt(i);
294      switch (c)
295      {
296        case '0':
297        case '1':
298        case '2':
299        case '3':
300        case '4':
301        case '5':
302        case '6':
303        case '7':
304        case '8':
305        case '9':
306          buffer.append(c);
307          break;
308
309        case '.':
310          if (buffer.length() == 0)
311          {
312            if (i == 0)
313            {
314              throw new ParseException(
315                   ERR_OID_STARTS_WITH_PERIOD.get(oidString), i);
316            }
317            else
318            {
319              throw new ParseException(
320                   ERR_OID_CONSECUTIVE_PERIODS.get(oidString, i), i);
321            }
322          }
323
324          if ((buffer.length() > 1) && (buffer.charAt(0) == '0'))
325          {
326            throw new ParseException(
327                 ERR_OID_LEADING_ZERO.get(oidString, buffer.toString()),
328                 componentStartPos);
329          }
330
331          try
332          {
333            components.add(Integer.parseInt(buffer.toString()));
334          }
335          catch (final Exception e)
336          {
337            Debug.debugException(e);
338            throw new ParseException(
339                 ERR_OID_CANNOT_PARSE_AS_INT.get( oidString, buffer.toString(),
340                      componentStartPos),
341                 componentStartPos);
342          }
343          buffer.setLength(0);
344          componentStartPos = (i + 1);
345          break;
346
347        default:
348          throw new ParseException(
349               ERR_OID_ILLEGAL_CHARACTER.get(oidString, c, i), i);
350      }
351    }
352
353    if (buffer.length() == 0)
354    {
355      throw new ParseException(
356           ERR_OID_ENDS_WITH_PERIOD.get(oidString), (oidString.length() - 1));
357    }
358
359    if ((buffer.length() > 1) && (buffer.charAt(0) == '0'))
360    {
361      throw new ParseException(
362           ERR_OID_LEADING_ZERO.get(oidString, buffer.toString()),
363           componentStartPos);
364    }
365
366    try
367    {
368      components.add(Integer.parseInt(buffer.toString()));
369    }
370    catch (final Exception e)
371    {
372      Debug.debugException(e);
373      throw new ParseException(
374           ERR_OID_CANNOT_PARSE_AS_INT.get( oidString, buffer.toString(),
375                componentStartPos),
376           componentStartPos);
377    }
378
379
380    if (strict)
381    {
382      if (components.size() < 2)
383      {
384        throw new ParseException(
385             ERR_OID_NOT_ENOUGH_COMPONENTS.get(oidString), 0);
386      }
387
388      final int firstComponent = components.get(0);
389      final int secondComponent = components.get(1);
390      switch (firstComponent)
391      {
392        case 0:
393        case 1:
394          if (secondComponent > 39)
395          {
396            throw new ParseException(
397                 ERR_OID_ILLEGAL_SECOND_COMPONENT.get(oidString,
398                      secondComponent, firstComponent),
399                 0);
400          }
401          break;
402
403        case 2:
404          // We don't need to do any more validation.
405          break;
406
407        default:
408          // Invalid value for the first component.
409          throw new ParseException(
410               ERR_OID_ILLEGAL_FIRST_COMPONENT.get(oidString, firstComponent),
411               0);
412      }
413    }
414
415    return new OID(oidString, components);
416  }
417
418
419
420  /**
421   * Indicates whether the provided string represents a valid numeric OID.  Note
422   * this this method only ensures that the value is made up of a dotted list of
423   * numbers that does not start or end with a period and does not contain two
424   * consecutive periods.  The {@link #isStrictlyValidNumericOID(String)} method
425   * performs additional validation, including ensuring that the OID contains
426   * at least two components, that the value of the first component is not
427   * greater than two, and that the value of the second component is not greater
428   * than 39 if the value of the first component is zero or one.
429   *
430   * @param  s  The string for which to make the determination.
431   *
432   * @return  {@code true} if the provided string represents a valid numeric
433   *          OID, or {@code false} if not.
434   */
435  public static boolean isValidNumericOID(@Nullable final String s)
436  {
437    return new OID(s).isValidNumericOID();
438  }
439
440
441
442  /**
443   * Indicates whether the provided string represents a valid numeric OID.  Note
444   * this this method only ensures that the value is made up of a dotted list of
445   * numbers that does not start or end with a period and does not contain two
446   * consecutive periods.  The {@link #isStrictlyValidNumericOID()} method
447   * performs additional validation, including ensuring that the OID contains
448   * at least two components, that the value of the first component is not
449   * greater than two, and that the value of the second component is not greater
450   * than 39 if the value of the first component is zero or one.
451   *
452   * @return  {@code true} if this object represents a valid numeric OID, or
453   *          {@code false} if not.
454   */
455  public boolean isValidNumericOID()
456  {
457    return (components != null);
458  }
459
460
461
462  /**
463   * Indicates whether this object represents a strictly valid numeric OID.
464   * In addition to ensuring that the value is made up of a dotted list of
465   * numbers that does not start or end with a period or contain two consecutive
466   * periods, this method also ensures that the OID contains at least two
467   * components, that the value of the first component is not greater than two,
468   * and that the value of the second component is not greater than 39 if the
469   * value of the first component is zero or one.
470   *
471   * @param  s  The string for which to make the determination.
472   *
473   * @return  {@code true} if this object represents a strictly valid numeric
474   *          OID, or {@code false} if not.
475   */
476  public static boolean isStrictlyValidNumericOID(@Nullable final String s)
477  {
478    return new OID(s).isStrictlyValidNumericOID();
479  }
480
481
482
483  /**
484   * Indicates whether this object represents a strictly valid numeric OID.
485   * In addition to ensuring that the value is made up of a dotted list of
486   * numbers that does not start or end with a period or contain two consecutive
487   * periods, this method also ensures that the OID contains at least two
488   * components, that the value of the first component is not greater than two,
489   * and that the value of the second component is not greater than 39 if the
490   * value of the first component is zero or one.
491   *
492   * @return  {@code true} if this object represents a strictly valid numeric
493   *          OID, or {@code false} if not.
494   */
495  public boolean isStrictlyValidNumericOID()
496  {
497    if ((components == null) || (components.size() < 2))
498    {
499      return false;
500    }
501
502    final int firstComponent = components.get(0);
503    final int secondComponent = components.get(1);
504    switch (firstComponent)
505    {
506      case 0:
507      case 1:
508        // The value of the second component must not be greater than 39.
509        return (secondComponent <= 39);
510
511      case 2:
512        // We don't need to do any more validation.
513        return true;
514
515      default:
516        // Invalid value for the first component.
517        return false;
518    }
519  }
520
521
522
523  /**
524   * Retrieves the numeric components that comprise this OID.  This will only
525   * return a non-{@code null} value if {@link #isValidNumericOID} returns
526   * {@code true}.
527   *
528   * @return  The numeric components that comprise this OID, or {@code null} if
529   *          this object does not represent a valid numeric OID.
530   */
531  @Nullable()
532  public List<Integer> getComponents()
533  {
534    return components;
535  }
536
537
538
539  /**
540   * Retrieves the OID that is the parent for this OID.  This OID must represent
541   * a valid numeric OID.
542   *
543   * @return  The OID that is the parent for this OID, or {@code null} if this
544   *          OID doesn't have a parent.  Note that the returned OID may not
545   *          necessarily be strictly valid in some cases (for example, if this
546   *          OID only contains two components, as all strictly valid OIDs must
547   *          contain at least two components).
548   *
549   * @throws  ParseException  If this OID does not represent a valid numeric
550   *                          OID.
551   */
552  @Nullable()
553  public OID getParent()
554         throws ParseException
555  {
556    if (components == null)
557    {
558      throw new ParseException(ERR_OID_GET_PARENT_NOT_VALID.get(oidString), 0);
559    }
560
561    if (components.size() <= 1)
562    {
563      // This OID cannot have a parent.
564      return null;
565    }
566
567    final List<Integer> childComponents = new ArrayList<>(components);
568    childComponents.remove(components.size() - 1);
569    return new OID(childComponents);
570  }
571
572
573
574  /**
575   * Indicates whether this OID is an ancestor of the provided OID.  This OID
576   * will be considered an ancestor of the provided OID if the provided OID has
577   * more components than this OID, and if the components that comprise this
578   * OID make up the initial set of components for the provided OID.
579   *
580   * @param  oid  The OID for which to make the determination.  It must not be
581   *              {@code null}, and it must represent a valid numeric OID.
582   *
583   * @return  {@code true} if this OID is an ancestor of the provided OID, or
584   *          {@code false} if not.
585   *
586   * @throws  ParseException  If either this OID or the provided OID does not
587   *                          represent a valid numeric OID.
588   */
589  public boolean isAncestorOf(@NotNull final OID oid)
590         throws ParseException
591  {
592    if (components == null)
593    {
594      throw new ParseException(
595           ERR_OID_IS_ANCESTOR_OF_THIS_NOT_VALID.get(oidString,
596                oid.oidString),
597           0);
598    }
599
600    if (oid.components == null)
601    {
602      throw new ParseException(
603           ERR_OID_IS_ANCESTOR_OF_PROVIDED_NOT_VALID.get(oid.oidString,
604                oid.oidString),
605           0);
606    }
607
608    if (oid.components.size() <= components.size())
609    {
610      return false;
611    }
612
613    for (int i=0; i < components.size(); i++)
614    {
615      if (! components.get(i).equals(oid.components.get(i)))
616      {
617        return false;
618      }
619    }
620
621    return true;
622  }
623
624
625
626  /**
627   * Indicates whether this OID is a descendant of the provided OID.  This OID
628   * will be considered a descendant of the provided OID if this OID has more
629   * components than the provided OID, and if the components that comprise the
630   * provided OID make up the initial set of components for this OID.
631   *
632   * @param  oid  The OID for which to make the determination.  It must not be
633   *              {@code null}, and it must represent a valid numeric OID.
634   *
635   * @return  {@code true} if this OID is a descendant of the provided OID, or
636   *          {@code false} if not.
637   *
638   * @throws  ParseException  If either this OID or the provided OID does not
639   *                          represent a valid numeric OID.
640   */
641  public boolean isDescendantOf(@NotNull final OID oid)
642         throws ParseException
643  {
644    if (components == null)
645    {
646      throw new ParseException(
647           ERR_OID_IS_DESCENDANT_OF_THIS_NOT_VALID.get(oidString,
648                oid.oidString),
649           0);
650    }
651
652    if (oid.components == null)
653    {
654      throw new ParseException(
655           ERR_OID_IS_DESCENDANT_OF_PROVIDED_NOT_VALID.get(oid.oidString,
656                oid.oidString),
657           0);
658    }
659
660    if (components.size() <= oid.components.size())
661    {
662      return false;
663    }
664
665    for (int i=0; i < oid.components.size(); i++)
666    {
667      if (! components.get(i).equals(oid.components.get(i)))
668      {
669        return false;
670      }
671    }
672
673    return true;
674  }
675
676
677
678  /**
679   * Retrieves a hash code for this OID.
680   *
681   * @return  A hash code for this OID.
682   */
683  @Override()
684  public int hashCode()
685  {
686    if (components == null)
687    {
688      return oidString.hashCode();
689    }
690    else
691    {
692      int hashCode = 0;
693      for (final int i : components)
694      {
695        hashCode += i;
696      }
697      return hashCode;
698    }
699  }
700
701
702
703  /**
704   * Indicates whether the provided object is equal to this OID.
705   *
706   * @param  o  The object for which to make the determination.
707   *
708   * @return  {@code true} if the provided object is equal to this OID, or
709   *          {@code false} if not.
710   */
711  @Override()
712  public boolean equals(@Nullable final Object o)
713  {
714    if (o == null)
715    {
716      return false;
717    }
718
719    if (o == this)
720    {
721      return true;
722    }
723
724    if (o instanceof OID)
725    {
726      final OID oid = (OID) o;
727      if (components == null)
728      {
729        return oidString.equals(oid.oidString);
730      }
731      else
732      {
733        return components.equals(oid.components);
734      }
735    }
736
737    return false;
738  }
739
740
741
742  /**
743   * Indicates the position of the provided object relative to this OID in a
744   * sorted list.
745   *
746   * @param  oid  The OID to compare against this OID.
747   *
748   * @return  A negative value if this OID should come before the provided OID
749   *          in a sorted list, a positive value if this OID should come after
750   *          the provided OID in a sorted list, or zero if the two OIDs
751   *          represent equivalent values.
752   */
753  @Override()
754  public int compareTo(@NotNull final OID oid)
755  {
756    if (components == null)
757    {
758      if (oid.components == null)
759      {
760        // Neither is a valid numeric OID, so we'll just compare the string
761        // representations.
762        return oidString.compareTo(oid.oidString);
763      }
764      else
765      {
766        // A valid numeric OID will always come before a non-valid one.
767        return 1;
768      }
769    }
770
771    if (oid.components == null)
772    {
773      // A valid numeric OID will always come before a non-valid one.
774      return -1;
775    }
776
777    for (int i=0; i < Math.min(components.size(), oid.components.size()); i++)
778    {
779      final int thisValue = components.get(i);
780      final int thatValue = oid.components.get(i);
781
782      if (thisValue < thatValue)
783      {
784        // This OID has a lower number in the first non-equal slot than the
785        // provided OID.
786        return -1;
787      }
788      else if (thisValue > thatValue)
789      {
790        // This OID has a higher number in the first non-equal slot than the
791        // provided OID.
792        return 1;
793      }
794    }
795
796    // Where the values overlap, they are equivalent.  Make the determination
797    // based on which is longer.
798    if (components.size() < oid.components.size())
799    {
800      // The provided OID is longer than this OID.
801      return -1;
802    }
803    else if (components.size() > oid.components.size())
804    {
805      // The provided OID is shorter than this OID.
806      return 1;
807    }
808    else
809    {
810      // They represent equivalent OIDs.
811      return 0;
812    }
813  }
814
815
816
817  /**
818   * Retrieves a string representation of this OID.
819   *
820   * @return  A string representation of this OID.
821   */
822  @Override()
823  @NotNull()
824  public String toString()
825  {
826    return oidString;
827  }
828}