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<>(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 = new ArrayList<>(components.length);
142    for (final int i : components)
143    {
144      compList.add(i);
145    }
146    return compList;
147  }
148
149
150
151  /**
152   * Parses the provided string as a numeric OID and extracts the numeric
153   * components from it.
154   *
155   * @param  oidString  The string to parse as a numeric OID.
156   *
157   * @return  The numeric components extracted from the provided string, or
158   *          {@code null} if the provided string does not represent a valid
159   *          numeric OID.
160   */
161  public static List<Integer> parseComponents(final String oidString)
162  {
163    if ((oidString == null) || oidString.isEmpty() ||
164        oidString.startsWith(".") || oidString.endsWith(".") ||
165        (oidString.indexOf("..") > 0))
166    {
167      return null;
168    }
169
170    final StringTokenizer tokenizer = new StringTokenizer(oidString, ".");
171    final ArrayList<Integer> compList = new ArrayList<>(10);
172    while (tokenizer.hasMoreTokens())
173    {
174      final String token = tokenizer.nextToken();
175      try
176      {
177        compList.add(Integer.parseInt(token));
178      }
179      catch (final Exception e)
180      {
181        Debug.debugException(e);
182        return null;
183      }
184    }
185
186    return Collections.unmodifiableList(compList);
187  }
188
189
190
191  /**
192   * Indicates whether the provided string represents a valid numeric OID.  Note
193   * this this method only ensures that the value is made up of a dotted list of
194   * numbers that does not start or end with a period and does not contain two
195   * consecutive periods.  The {@link #isStrictlyValidNumericOID(String)} method
196   * performs additional validation, including ensuring that the OID contains
197   * at least two components, that the value of the first component is not
198   * greater than two, and that the value of the second component is not greater
199   * than 39 if the value of the first component is zero or one.
200   *
201   * @param  s  The string for which to make the determination.
202   *
203   * @return  {@code true} if the provided string represents a valid numeric
204   *          OID, or {@code false} if not.
205   */
206  public static boolean isValidNumericOID(final String s)
207  {
208    return new OID(s).isValidNumericOID();
209  }
210
211
212
213  /**
214   * Indicates whether the provided string represents a valid numeric OID.  Note
215   * this this method only ensures that the value is made up of a dotted list of
216   * numbers that does not start or end with a period and does not contain two
217   * consecutive periods.  The {@link #isStrictlyValidNumericOID()} method
218   * performs additional validation, including ensuring that the OID contains
219   * at least two components, that the value of the first component is not
220   * greater than two, and that the value of the second component is not greater
221   * than 39 if the value of the first component is zero or one.
222   *
223   * @return  {@code true} if this object represents a valid numeric OID, or
224   *          {@code false} if not.
225   */
226  public boolean isValidNumericOID()
227  {
228    return (components != null);
229  }
230
231
232
233  /**
234   * Indicates whether this object represents a strictly valid numeric OID.
235   * In addition to ensuring that the value is made up of a dotted list of
236   * numbers that does not start or end with a period or contain two consecutive
237   * periods, this method also ensures that the OID contains at least two
238   * components, that the value of the first component is not greater than two,
239   * and that the value of the second component is not greater than 39 if the
240   * value of the first component is zero or one.
241   *
242   * @param  s  The string for which to make the determination.
243   *
244   * @return  {@code true} if this object represents a strictly valid numeric
245   *          OID, or {@code false} if not.
246   */
247  public static boolean isStrictlyValidNumericOID(final String s)
248  {
249    return new OID(s).isStrictlyValidNumericOID();
250  }
251
252
253
254  /**
255   * Indicates whether this object represents a strictly valid numeric OID.
256   * In addition to ensuring that the value is made up of a dotted list of
257   * numbers that does not start or end with a period or contain two consecutive
258   * periods, this method also ensures that the OID contains at least two
259   * components, that the value of the first component is not greater than two,
260   * and that the value of the second component is not greater than 39 if the
261   * value of the first component is zero or one.
262   *
263   * @return  {@code true} if this object represents a strictly valid numeric
264   *          OID, or {@code false} if not.
265   */
266  public boolean isStrictlyValidNumericOID()
267  {
268    if ((components == null) || (components.size() < 2))
269    {
270      return false;
271    }
272
273    final int firstComponent = components.get(0);
274    final int secondComponent = components.get(1);
275    switch (firstComponent)
276    {
277      case 0:
278      case 1:
279        // The value of the second component must not be greater than 39.
280        return (secondComponent <= 39);
281
282      case 2:
283        // We don't need to do any more validation.
284        return true;
285
286      default:
287        // Invalid value for the first component.
288        return false;
289    }
290  }
291
292
293
294  /**
295   * Retrieves the numeric components that comprise this OID.  This will only
296   * return a non-{@code null} value if {@link #isValidNumericOID} returns
297   * {@code true}.
298   *
299   * @return  The numeric components that comprise this OID, or {@code null} if
300   *          this object does not represent a valid numeric OID.
301   */
302  public List<Integer> getComponents()
303  {
304    return components;
305  }
306
307
308
309  /**
310   * Retrieves a hash code for this OID.
311   *
312   * @return  A hash code for this OID.
313   */
314  @Override()
315  public int hashCode()
316  {
317    if (components == null)
318    {
319      return oidString.hashCode();
320    }
321    else
322    {
323      int hashCode = 0;
324      for (final int i : components)
325      {
326        hashCode += i;
327      }
328      return hashCode;
329    }
330  }
331
332
333
334  /**
335   * Indicates whether the provided object is equal to this OID.
336   *
337   * @param  o  The object for which to make the determination.
338   *
339   * @return  {@code true} if the provided object is equal to this OID, or
340   *          {@code false} if not.
341   */
342  @Override()
343  public boolean equals(final Object o)
344  {
345    if (o == null)
346    {
347      return false;
348    }
349
350    if (o == this)
351    {
352      return true;
353    }
354
355    if (o instanceof OID)
356    {
357      final OID oid = (OID) o;
358      if (components == null)
359      {
360        return oidString.equals(oid.oidString);
361      }
362      else
363      {
364        return components.equals(oid.components);
365      }
366    }
367
368    return false;
369  }
370
371
372
373  /**
374   * Indicates the position of the provided object relative to this OID in a
375   * sorted list.
376   *
377   * @param  oid  The OID to compare against this OID.
378   *
379   * @return  A negative value if this OID should come before the provided OID
380   *          in a sorted list, a positive value if this OID should come after
381   *          the provided OID in a sorted list, or zero if the two OIDs
382   *          represent equivalent values.
383   */
384  @Override()
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}