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}