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