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.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 object with the provided string representation and set 147 * of components. 148 * 149 * @param oidString The string representation of this OID. 150 * @param components The numeric components for this OID. 151 */ 152 private OID(@NotNull final String oidString, 153 @NotNull final List<Integer> components) 154 { 155 this.oidString = oidString; 156 this.components = Collections.unmodifiableList(components); 157 } 158 159 160 161 /** 162 * Retrieves a list corresponding to the elements in the provided array. 163 * 164 * @param components The array to convert to a list. 165 * 166 * @return The list of elements. 167 */ 168 @Nullable() 169 private static List<Integer> toList(@Nullable final int... components) 170 { 171 if (components == null) 172 { 173 return null; 174 } 175 176 final ArrayList<Integer> compList = new ArrayList<>(components.length); 177 for (final int i : components) 178 { 179 compList.add(i); 180 } 181 return compList; 182 } 183 184 185 186 /** 187 * Parses the provided string as a numeric OID and extracts the numeric 188 * components from it. 189 * 190 * @param oidString The string to parse as a numeric OID. 191 * 192 * @return The numeric components extracted from the provided string, or 193 * {@code null} if the provided string does not represent a valid 194 * numeric OID. 195 */ 196 @Nullable() 197 public static List<Integer> parseComponents(@Nullable final String oidString) 198 { 199 if ((oidString == null) || oidString.isEmpty() || 200 oidString.startsWith(".") || oidString.endsWith(".") || 201 (oidString.indexOf("..") > 0)) 202 { 203 return null; 204 } 205 206 final StringTokenizer tokenizer = new StringTokenizer(oidString, "."); 207 final ArrayList<Integer> compList = new ArrayList<>(10); 208 while (tokenizer.hasMoreTokens()) 209 { 210 final String token = tokenizer.nextToken(); 211 try 212 { 213 compList.add(Integer.parseInt(token)); 214 } 215 catch (final Exception e) 216 { 217 Debug.debugException(e); 218 return null; 219 } 220 } 221 222 return Collections.unmodifiableList(compList); 223 } 224 225 226 227 /** 228 * Parses the provided string as a numeric OID, optionally using additional 229 * strict validation. 230 * 231 * @param oidString The string to be parsed as a numeric OID. It must not 232 * be {@code null}. 233 * @param strict Indicates whether to use strict validation. If this is 234 * {@code false}, then the method will verify that the 235 * provided string is made up of a dotted list of numbers 236 * that does not start or end with a period and does not 237 * contain consecutive periods. If this is {@code true}, 238 * then it will additional verify that the OID contains at 239 * least two components, that the value of the first 240 * component is not greater than two, and that the value of 241 * the second component is not greater than 39 if the value 242 * of the first component is zero or one. 243 * 244 * @return The OID that was parsed from the provided string. 245 * 246 * @throws ParseException If the provided string cannot be parsed as a valid 247 * numeric OID. 248 */ 249 @NotNull() 250 public static OID parseNumericOID(@Nullable final String oidString, 251 final boolean strict) 252 throws ParseException 253 { 254 if ((oidString == null) || oidString.isEmpty()) 255 { 256 throw new ParseException(ERR_OID_EMPTY.get(), 0); 257 } 258 259 int componentStartPos = 0; 260 final List<Integer> components = new ArrayList<>(oidString.length()); 261 final StringBuilder buffer = new StringBuilder(oidString.length()); 262 for (int i=0; i < oidString.length(); i++) 263 { 264 final char c = oidString.charAt(i); 265 switch (c) 266 { 267 case '0': 268 case '1': 269 case '2': 270 case '3': 271 case '4': 272 case '5': 273 case '6': 274 case '7': 275 case '8': 276 case '9': 277 buffer.append(c); 278 break; 279 280 case '.': 281 if (buffer.length() == 0) 282 { 283 if (i == 0) 284 { 285 throw new ParseException( 286 ERR_OID_STARTS_WITH_PERIOD.get(oidString), i); 287 } 288 else 289 { 290 throw new ParseException( 291 ERR_OID_CONSECUTIVE_PERIODS.get(oidString, i), i); 292 } 293 } 294 295 if ((buffer.length() > 1) && (buffer.charAt(0) == '0')) 296 { 297 throw new ParseException( 298 ERR_OID_LEADING_ZERO.get(oidString, buffer.toString()), 299 componentStartPos); 300 } 301 302 try 303 { 304 components.add(Integer.parseInt(buffer.toString())); 305 } 306 catch (final Exception e) 307 { 308 Debug.debugException(e); 309 throw new ParseException( 310 ERR_OID_CANNOT_PARSE_AS_INT.get( oidString, buffer.toString(), 311 componentStartPos), 312 componentStartPos); 313 } 314 buffer.setLength(0); 315 componentStartPos = (i + 1); 316 break; 317 318 default: 319 throw new ParseException( 320 ERR_OID_ILLEGAL_CHARACTER.get(oidString, c, i), i); 321 } 322 } 323 324 if (buffer.length() == 0) 325 { 326 throw new ParseException( 327 ERR_OID_ENDS_WITH_PERIOD.get(oidString), (oidString.length() - 1)); 328 } 329 330 if ((buffer.length() > 1) && (buffer.charAt(0) == '0')) 331 { 332 throw new ParseException( 333 ERR_OID_LEADING_ZERO.get(oidString, buffer.toString()), 334 componentStartPos); 335 } 336 337 try 338 { 339 components.add(Integer.parseInt(buffer.toString())); 340 } 341 catch (final Exception e) 342 { 343 Debug.debugException(e); 344 throw new ParseException( 345 ERR_OID_CANNOT_PARSE_AS_INT.get( oidString, buffer.toString(), 346 componentStartPos), 347 componentStartPos); 348 } 349 350 351 if (strict) 352 { 353 if (components.size() < 2) 354 { 355 throw new ParseException( 356 ERR_OID_NOT_ENOUGH_COMPONENTS.get(oidString), 0); 357 } 358 359 final int firstComponent = components.get(0); 360 final int secondComponent = components.get(1); 361 switch (firstComponent) 362 { 363 case 0: 364 case 1: 365 if (secondComponent > 39) 366 { 367 throw new ParseException( 368 ERR_OID_ILLEGAL_SECOND_COMPONENT.get(oidString, 369 secondComponent, firstComponent), 370 0); 371 } 372 break; 373 374 case 2: 375 // We don't need to do any more validation. 376 break; 377 378 default: 379 // Invalid value for the first component. 380 throw new ParseException( 381 ERR_OID_ILLEGAL_FIRST_COMPONENT.get(oidString, firstComponent), 382 0); 383 } 384 } 385 386 return new OID(oidString, components); 387 } 388 389 390 391 /** 392 * Indicates whether the provided string represents a valid numeric OID. Note 393 * this this method only ensures that the value is made up of a dotted list of 394 * numbers that does not start or end with a period and does not contain two 395 * consecutive periods. The {@link #isStrictlyValidNumericOID(String)} method 396 * performs additional validation, including ensuring that the OID contains 397 * at least two components, that the value of the first component is not 398 * greater than two, and that the value of the second component is not greater 399 * than 39 if the value of the first component is zero or one. 400 * 401 * @param s The string for which to make the determination. 402 * 403 * @return {@code true} if the provided string represents a valid numeric 404 * OID, or {@code false} if not. 405 */ 406 public static boolean isValidNumericOID(@Nullable final String s) 407 { 408 return new OID(s).isValidNumericOID(); 409 } 410 411 412 413 /** 414 * Indicates whether the provided string represents a valid numeric OID. Note 415 * this this method only ensures that the value is made up of a dotted list of 416 * numbers that does not start or end with a period and does not contain two 417 * consecutive periods. The {@link #isStrictlyValidNumericOID()} method 418 * performs additional validation, including ensuring that the OID contains 419 * at least two components, that the value of the first component is not 420 * greater than two, and that the value of the second component is not greater 421 * than 39 if the value of the first component is zero or one. 422 * 423 * @return {@code true} if this object represents a valid numeric OID, or 424 * {@code false} if not. 425 */ 426 public boolean isValidNumericOID() 427 { 428 return (components != null); 429 } 430 431 432 433 /** 434 * Indicates whether this object represents a strictly valid numeric OID. 435 * In addition to ensuring that the value is made up of a dotted list of 436 * numbers that does not start or end with a period or contain two consecutive 437 * periods, this method also ensures that the OID contains at least two 438 * components, that the value of the first component is not greater than two, 439 * and that the value of the second component is not greater than 39 if the 440 * value of the first component is zero or one. 441 * 442 * @param s The string for which to make the determination. 443 * 444 * @return {@code true} if this object represents a strictly valid numeric 445 * OID, or {@code false} if not. 446 */ 447 public static boolean isStrictlyValidNumericOID(@Nullable final String s) 448 { 449 return new OID(s).isStrictlyValidNumericOID(); 450 } 451 452 453 454 /** 455 * Indicates whether this object represents a strictly valid numeric OID. 456 * In addition to ensuring that the value is made up of a dotted list of 457 * numbers that does not start or end with a period or contain two consecutive 458 * periods, this method also ensures that the OID contains at least two 459 * components, that the value of the first component is not greater than two, 460 * and that the value of the second component is not greater than 39 if the 461 * value of the first component is zero or one. 462 * 463 * @return {@code true} if this object represents a strictly valid numeric 464 * OID, or {@code false} if not. 465 */ 466 public boolean isStrictlyValidNumericOID() 467 { 468 if ((components == null) || (components.size() < 2)) 469 { 470 return false; 471 } 472 473 final int firstComponent = components.get(0); 474 final int secondComponent = components.get(1); 475 switch (firstComponent) 476 { 477 case 0: 478 case 1: 479 // The value of the second component must not be greater than 39. 480 return (secondComponent <= 39); 481 482 case 2: 483 // We don't need to do any more validation. 484 return true; 485 486 default: 487 // Invalid value for the first component. 488 return false; 489 } 490 } 491 492 493 494 /** 495 * Retrieves the numeric components that comprise this OID. This will only 496 * return a non-{@code null} value if {@link #isValidNumericOID} returns 497 * {@code true}. 498 * 499 * @return The numeric components that comprise this OID, or {@code null} if 500 * this object does not represent a valid numeric OID. 501 */ 502 @Nullable() 503 public List<Integer> getComponents() 504 { 505 return components; 506 } 507 508 509 510 /** 511 * Retrieves a hash code for this OID. 512 * 513 * @return A hash code for this OID. 514 */ 515 @Override() 516 public int hashCode() 517 { 518 if (components == null) 519 { 520 return oidString.hashCode(); 521 } 522 else 523 { 524 int hashCode = 0; 525 for (final int i : components) 526 { 527 hashCode += i; 528 } 529 return hashCode; 530 } 531 } 532 533 534 535 /** 536 * Indicates whether the provided object is equal to this OID. 537 * 538 * @param o The object for which to make the determination. 539 * 540 * @return {@code true} if the provided object is equal to this OID, or 541 * {@code false} if not. 542 */ 543 @Override() 544 public boolean equals(@Nullable final Object o) 545 { 546 if (o == null) 547 { 548 return false; 549 } 550 551 if (o == this) 552 { 553 return true; 554 } 555 556 if (o instanceof OID) 557 { 558 final OID oid = (OID) o; 559 if (components == null) 560 { 561 return oidString.equals(oid.oidString); 562 } 563 else 564 { 565 return components.equals(oid.components); 566 } 567 } 568 569 return false; 570 } 571 572 573 574 /** 575 * Indicates the position of the provided object relative to this OID in a 576 * sorted list. 577 * 578 * @param oid The OID to compare against this OID. 579 * 580 * @return A negative value if this OID should come before the provided OID 581 * in a sorted list, a positive value if this OID should come after 582 * the provided OID in a sorted list, or zero if the two OIDs 583 * represent equivalent values. 584 */ 585 @Override() 586 public int compareTo(@NotNull final OID oid) 587 { 588 if (components == null) 589 { 590 if (oid.components == null) 591 { 592 // Neither is a valid numeric OID, so we'll just compare the string 593 // representations. 594 return oidString.compareTo(oid.oidString); 595 } 596 else 597 { 598 // A valid numeric OID will always come before a non-valid one. 599 return 1; 600 } 601 } 602 603 if (oid.components == null) 604 { 605 // A valid numeric OID will always come before a non-valid one. 606 return -1; 607 } 608 609 for (int i=0; i < Math.min(components.size(), oid.components.size()); i++) 610 { 611 final int thisValue = components.get(i); 612 final int thatValue = oid.components.get(i); 613 614 if (thisValue < thatValue) 615 { 616 // This OID has a lower number in the first non-equal slot than the 617 // provided OID. 618 return -1; 619 } 620 else if (thisValue > thatValue) 621 { 622 // This OID has a higher number in the first non-equal slot than the 623 // provided OID. 624 return 1; 625 } 626 } 627 628 // Where the values overlap, they are equivalent. Make the determination 629 // based on which is longer. 630 if (components.size() < oid.components.size()) 631 { 632 // The provided OID is longer than this OID. 633 return -1; 634 } 635 else if (components.size() > oid.components.size()) 636 { 637 // The provided OID is shorter than this OID. 638 return 1; 639 } 640 else 641 { 642 // They represent equivalent OIDs. 643 return 0; 644 } 645 } 646 647 648 649 /** 650 * Retrieves a string representation of this OID. 651 * 652 * @return A string representation of this OID. 653 */ 654 @Override() 655 @NotNull() 656 public String toString() 657 { 658 return oidString; 659 } 660}