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}