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.util.ArrayList;
042import java.util.Collections;
043import java.util.List;
044import java.util.StringTokenizer;
045
046
047
048/**
049 * This class provides a data structure that may be used for representing object
050 * identifiers.  Since some directory servers support using strings that aren't
051 * valid object identifiers where OIDs are required, this implementation
052 * supports arbitrary strings, but some methods may only be available for valid
053 * OIDs.
054 */
055@NotMutable()
056@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
057public final class OID
058       implements Serializable, Comparable<OID>
059{
060  /**
061   * The serial version UID for this serializable class.
062   */
063  private static final long serialVersionUID = -4542498394670806081L;
064
065
066
067  // The numeric components that comprise this OID.
068  private final List<Integer> components;
069
070  // The string representation for this OID.
071  private final String oidString;
072
073
074
075  /**
076   * Creates a new OID object from the provided string representation.
077   *
078   * @param  oidString  The string to use to create this OID.
079   */
080  public OID(final String oidString)
081  {
082    if (oidString == null)
083    {
084      this.oidString = "";
085    }
086    else
087    {
088      this.oidString = oidString;
089    }
090
091    components = parseComponents(oidString);
092  }
093
094
095
096  /**
097   * Creates a new OID object from the provided set of numeric components.  At
098   * least one component must be provided for a valid OID.
099   *
100   * @param  components  The numeric components to include in the OID.
101   */
102  public OID(final int... components)
103  {
104    this(toList(components));
105  }
106
107
108
109  /**
110   * Creates a new OID object from the provided set of numeric components.  At
111   * least one component must be provided for a valid OID.
112   *
113   * @param  components  The numeric components to include in the OID.
114   */
115  public OID(final List<Integer> components)
116  {
117    if ((components == null) || components.isEmpty())
118    {
119      this.components = null;
120      oidString = "";
121    }
122    else
123    {
124      this.components =
125           Collections.unmodifiableList(new ArrayList<>(components));
126
127      final StringBuilder buffer = new StringBuilder();
128      for (final Integer i : components)
129      {
130        if (buffer.length() > 0)
131        {
132          buffer.append('.');
133        }
134        buffer.append(i);
135      }
136      oidString = buffer.toString();
137    }
138  }
139
140
141
142  /**
143   * Retrieves a list corresponding to the elements in the provided array.
144   *
145   * @param  components  The array to convert to a list.
146   *
147   * @return  The list of elements.
148   */
149  private static List<Integer> toList(final int... components)
150  {
151    if (components == null)
152    {
153      return null;
154    }
155
156    final ArrayList<Integer> compList = new ArrayList<>(components.length);
157    for (final int i : components)
158    {
159      compList.add(i);
160    }
161    return compList;
162  }
163
164
165
166  /**
167   * Parses the provided string as a numeric OID and extracts the numeric
168   * components from it.
169   *
170   * @param  oidString  The string to parse as a numeric OID.
171   *
172   * @return  The numeric components extracted from the provided string, or
173   *          {@code null} if the provided string does not represent a valid
174   *          numeric OID.
175   */
176  public static List<Integer> parseComponents(final String oidString)
177  {
178    if ((oidString == null) || oidString.isEmpty() ||
179        oidString.startsWith(".") || oidString.endsWith(".") ||
180        (oidString.indexOf("..") > 0))
181    {
182      return null;
183    }
184
185    final StringTokenizer tokenizer = new StringTokenizer(oidString, ".");
186    final ArrayList<Integer> compList = new ArrayList<>(10);
187    while (tokenizer.hasMoreTokens())
188    {
189      final String token = tokenizer.nextToken();
190      try
191      {
192        compList.add(Integer.parseInt(token));
193      }
194      catch (final Exception e)
195      {
196        Debug.debugException(e);
197        return null;
198      }
199    }
200
201    return Collections.unmodifiableList(compList);
202  }
203
204
205
206  /**
207   * Indicates whether the provided string represents a valid numeric OID.  Note
208   * this this method only ensures that the value is made up of a dotted list of
209   * numbers that does not start or end with a period and does not contain two
210   * consecutive periods.  The {@link #isStrictlyValidNumericOID(String)} method
211   * performs additional validation, including ensuring that the OID contains
212   * at least two components, that the value of the first component is not
213   * greater than two, and that the value of the second component is not greater
214   * than 39 if the value of the first component is zero or one.
215   *
216   * @param  s  The string for which to make the determination.
217   *
218   * @return  {@code true} if the provided string represents a valid numeric
219   *          OID, or {@code false} if not.
220   */
221  public static boolean isValidNumericOID(final String s)
222  {
223    return new OID(s).isValidNumericOID();
224  }
225
226
227
228  /**
229   * Indicates whether the provided string represents a valid numeric OID.  Note
230   * this this method only ensures that the value is made up of a dotted list of
231   * numbers that does not start or end with a period and does not contain two
232   * consecutive periods.  The {@link #isStrictlyValidNumericOID()} method
233   * performs additional validation, including ensuring that the OID contains
234   * at least two components, that the value of the first component is not
235   * greater than two, and that the value of the second component is not greater
236   * than 39 if the value of the first component is zero or one.
237   *
238   * @return  {@code true} if this object represents a valid numeric OID, or
239   *          {@code false} if not.
240   */
241  public boolean isValidNumericOID()
242  {
243    return (components != null);
244  }
245
246
247
248  /**
249   * Indicates whether this object represents a strictly valid numeric OID.
250   * In addition to ensuring that the value is made up of a dotted list of
251   * numbers that does not start or end with a period or contain two consecutive
252   * periods, this method also ensures that the OID contains at least two
253   * components, that the value of the first component is not greater than two,
254   * and that the value of the second component is not greater than 39 if the
255   * value of the first component is zero or one.
256   *
257   * @param  s  The string for which to make the determination.
258   *
259   * @return  {@code true} if this object represents a strictly valid numeric
260   *          OID, or {@code false} if not.
261   */
262  public static boolean isStrictlyValidNumericOID(final String s)
263  {
264    return new OID(s).isStrictlyValidNumericOID();
265  }
266
267
268
269  /**
270   * Indicates whether this object represents a strictly valid numeric OID.
271   * In addition to ensuring that the value is made up of a dotted list of
272   * numbers that does not start or end with a period or contain two consecutive
273   * periods, this method also ensures that the OID contains at least two
274   * components, that the value of the first component is not greater than two,
275   * and that the value of the second component is not greater than 39 if the
276   * value of the first component is zero or one.
277   *
278   * @return  {@code true} if this object represents a strictly valid numeric
279   *          OID, or {@code false} if not.
280   */
281  public boolean isStrictlyValidNumericOID()
282  {
283    if ((components == null) || (components.size() < 2))
284    {
285      return false;
286    }
287
288    final int firstComponent = components.get(0);
289    final int secondComponent = components.get(1);
290    switch (firstComponent)
291    {
292      case 0:
293      case 1:
294        // The value of the second component must not be greater than 39.
295        return (secondComponent <= 39);
296
297      case 2:
298        // We don't need to do any more validation.
299        return true;
300
301      default:
302        // Invalid value for the first component.
303        return false;
304    }
305  }
306
307
308
309  /**
310   * Retrieves the numeric components that comprise this OID.  This will only
311   * return a non-{@code null} value if {@link #isValidNumericOID} returns
312   * {@code true}.
313   *
314   * @return  The numeric components that comprise this OID, or {@code null} if
315   *          this object does not represent a valid numeric OID.
316   */
317  public List<Integer> getComponents()
318  {
319    return components;
320  }
321
322
323
324  /**
325   * Retrieves a hash code for this OID.
326   *
327   * @return  A hash code for this OID.
328   */
329  @Override()
330  public int hashCode()
331  {
332    if (components == null)
333    {
334      return oidString.hashCode();
335    }
336    else
337    {
338      int hashCode = 0;
339      for (final int i : components)
340      {
341        hashCode += i;
342      }
343      return hashCode;
344    }
345  }
346
347
348
349  /**
350   * Indicates whether the provided object is equal to this OID.
351   *
352   * @param  o  The object for which to make the determination.
353   *
354   * @return  {@code true} if the provided object is equal to this OID, or
355   *          {@code false} if not.
356   */
357  @Override()
358  public boolean equals(final Object o)
359  {
360    if (o == null)
361    {
362      return false;
363    }
364
365    if (o == this)
366    {
367      return true;
368    }
369
370    if (o instanceof OID)
371    {
372      final OID oid = (OID) o;
373      if (components == null)
374      {
375        return oidString.equals(oid.oidString);
376      }
377      else
378      {
379        return components.equals(oid.components);
380      }
381    }
382
383    return false;
384  }
385
386
387
388  /**
389   * Indicates the position of the provided object relative to this OID in a
390   * sorted list.
391   *
392   * @param  oid  The OID to compare against this OID.
393   *
394   * @return  A negative value if this OID should come before the provided OID
395   *          in a sorted list, a positive value if this OID should come after
396   *          the provided OID in a sorted list, or zero if the two OIDs
397   *          represent equivalent values.
398   */
399  @Override()
400  public int compareTo(final OID oid)
401  {
402    if (components == null)
403    {
404      if (oid.components == null)
405      {
406        // Neither is a valid numeric OID, so we'll just compare the string
407        // representations.
408        return oidString.compareTo(oid.oidString);
409      }
410      else
411      {
412        // A valid numeric OID will always come before a non-valid one.
413        return 1;
414      }
415    }
416
417    if (oid.components == null)
418    {
419      // A valid numeric OID will always come before a non-valid one.
420      return -1;
421    }
422
423    for (int i=0; i < Math.min(components.size(), oid.components.size()); i++)
424    {
425      final int thisValue = components.get(i);
426      final int thatValue = oid.components.get(i);
427
428      if (thisValue < thatValue)
429      {
430        // This OID has a lower number in the first non-equal slot than the
431        // provided OID.
432        return -1;
433      }
434      else if (thisValue > thatValue)
435      {
436        // This OID has a higher number in the first non-equal slot than the
437        // provided OID.
438        return 1;
439      }
440    }
441
442    // Where the values overlap, they are equivalent.  Make the determination
443    // based on which is longer.
444    if (components.size() < oid.components.size())
445    {
446      // The provided OID is longer than this OID.
447      return -1;
448    }
449    else if (components.size() > oid.components.size())
450    {
451      // The provided OID is shorter than this OID.
452      return 1;
453    }
454    else
455    {
456      // They represent equivalent OIDs.
457      return 0;
458    }
459  }
460
461
462
463  /**
464   * Retrieves a string representation of this OID.
465   *
466   * @return  A string representation of this OID.
467   */
468  @Override()
469  public String toString()
470  {
471    return oidString;
472  }
473}