001/* 002 * Copyright 2007-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-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) 2007-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.ldap.sdk.schema; 037 038 039 040import java.util.ArrayList; 041import java.util.Collection; 042import java.util.Collections; 043import java.util.HashSet; 044import java.util.Map; 045import java.util.LinkedHashMap; 046import java.util.LinkedHashSet; 047import java.util.Set; 048 049import com.unboundid.ldap.sdk.LDAPException; 050import com.unboundid.ldap.sdk.ResultCode; 051import com.unboundid.util.NotMutable; 052import com.unboundid.util.NotNull; 053import com.unboundid.util.Nullable; 054import com.unboundid.util.StaticUtils; 055import com.unboundid.util.ThreadSafety; 056import com.unboundid.util.ThreadSafetyLevel; 057import com.unboundid.util.Validator; 058 059import static com.unboundid.ldap.sdk.schema.SchemaMessages.*; 060 061 062 063/** 064 * This class provides a data structure that describes an LDAP object class 065 * schema element. 066 */ 067@NotMutable() 068@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 069public final class ObjectClassDefinition 070 extends SchemaElement 071{ 072 /** 073 * The serial version UID for this serializable class. 074 */ 075 private static final long serialVersionUID = -3024333376249332728L; 076 077 078 079 // Indicates whether this object class is declared obsolete. 080 private final boolean isObsolete; 081 082 // The set of extensions for this object class. 083 @NotNull private final Map<String,String[]> extensions; 084 085 // The object class type for this object class. 086 @Nullable private final ObjectClassType objectClassType; 087 088 // The description for this object class. 089 @Nullable private final String description; 090 091 // The string representation of this object class. 092 @NotNull private final String objectClassString; 093 094 // The OID for this object class. 095 @NotNull private final String oid; 096 097 // The set of names for this object class. 098 @NotNull private final String[] names; 099 100 // The names/OIDs of the optional attributes. 101 @NotNull private final String[] optionalAttributes; 102 103 // The names/OIDs of the required attributes. 104 @NotNull private final String[] requiredAttributes; 105 106 // The set of superior object class names/OIDs. 107 @NotNull private final String[] superiorClasses; 108 109 110 111 /** 112 * Creates a new object class from the provided string representation. 113 * 114 * @param s The string representation of the object class to create, using 115 * the syntax described in RFC 4512 section 4.1.1. It must not be 116 * {@code null}. 117 * 118 * @throws LDAPException If the provided string cannot be decoded as an 119 * object class definition. 120 */ 121 public ObjectClassDefinition(@NotNull final String s) 122 throws LDAPException 123 { 124 Validator.ensureNotNull(s); 125 126 objectClassString = s.trim(); 127 128 // The first character must be an opening parenthesis. 129 final int length = objectClassString.length(); 130 if (length == 0) 131 { 132 throw new LDAPException(ResultCode.DECODING_ERROR, 133 ERR_OC_DECODE_EMPTY.get()); 134 } 135 else if (objectClassString.charAt(0) != '(') 136 { 137 throw new LDAPException(ResultCode.DECODING_ERROR, 138 ERR_OC_DECODE_NO_OPENING_PAREN.get( 139 objectClassString)); 140 } 141 142 143 // Skip over any spaces until we reach the start of the OID, then read the 144 // OID until we find the next space. 145 int pos = skipSpaces(objectClassString, 1, length); 146 147 StringBuilder buffer = new StringBuilder(); 148 pos = readOID(objectClassString, pos, length, buffer); 149 oid = buffer.toString(); 150 151 152 // Technically, object class elements are supposed to appear in a specific 153 // order, but we'll be lenient and allow remaining elements to come in any 154 // order. 155 final ArrayList<String> nameList = new ArrayList<>(1); 156 final ArrayList<String> supList = new ArrayList<>(1); 157 final ArrayList<String> reqAttrs = new ArrayList<>(20); 158 final ArrayList<String> optAttrs = new ArrayList<>(20); 159 final Map<String,String[]> exts = 160 new LinkedHashMap<>(StaticUtils.computeMapCapacity(5)); 161 Boolean obsolete = null; 162 ObjectClassType ocType = null; 163 String descr = null; 164 165 while (true) 166 { 167 // Skip over any spaces until we find the next element. 168 pos = skipSpaces(objectClassString, pos, length); 169 170 // Read until we find the next space or the end of the string. Use that 171 // token to figure out what to do next. 172 final int tokenStartPos = pos; 173 while ((pos < length) && (objectClassString.charAt(pos) != ' ')) 174 { 175 pos++; 176 } 177 178 // It's possible that the token could be smashed right up against the 179 // closing parenthesis. If that's the case, then extract just the token 180 // and handle the closing parenthesis the next time through. 181 String token = objectClassString.substring(tokenStartPos, pos); 182 if ((token.length() > 1) && (token.endsWith(")"))) 183 { 184 token = token.substring(0, token.length() - 1); 185 pos--; 186 } 187 188 final String lowerToken = StaticUtils.toLowerCase(token); 189 if (lowerToken.equals(")")) 190 { 191 // This indicates that we're at the end of the value. There should not 192 // be any more closing characters. 193 if (pos < length) 194 { 195 throw new LDAPException(ResultCode.DECODING_ERROR, 196 ERR_OC_DECODE_CLOSE_NOT_AT_END.get( 197 objectClassString)); 198 } 199 break; 200 } 201 else if (lowerToken.equals("name")) 202 { 203 if (nameList.isEmpty()) 204 { 205 pos = skipSpaces(objectClassString, pos, length); 206 pos = readQDStrings(objectClassString, pos, length, token, nameList); 207 } 208 else 209 { 210 throw new LDAPException(ResultCode.DECODING_ERROR, 211 ERR_OC_DECODE_MULTIPLE_ELEMENTS.get( 212 objectClassString, "NAME")); 213 } 214 } 215 else if (lowerToken.equals("desc")) 216 { 217 if (descr == null) 218 { 219 pos = skipSpaces(objectClassString, pos, length); 220 221 buffer = new StringBuilder(); 222 pos = readQDString(objectClassString, pos, length, token, buffer); 223 descr = buffer.toString(); 224 } 225 else 226 { 227 throw new LDAPException(ResultCode.DECODING_ERROR, 228 ERR_OC_DECODE_MULTIPLE_ELEMENTS.get( 229 objectClassString, "DESC")); 230 } 231 } 232 else if (lowerToken.equals("obsolete")) 233 { 234 if (obsolete == null) 235 { 236 obsolete = true; 237 } 238 else 239 { 240 throw new LDAPException(ResultCode.DECODING_ERROR, 241 ERR_OC_DECODE_MULTIPLE_ELEMENTS.get( 242 objectClassString, "OBSOLETE")); 243 } 244 } 245 else if (lowerToken.equals("sup")) 246 { 247 if (supList.isEmpty()) 248 { 249 pos = skipSpaces(objectClassString, pos, length); 250 pos = readOIDs(objectClassString, pos, length, token, supList); 251 } 252 else 253 { 254 throw new LDAPException(ResultCode.DECODING_ERROR, 255 ERR_OC_DECODE_MULTIPLE_ELEMENTS.get( 256 objectClassString, "SUP")); 257 } 258 } 259 else if (lowerToken.equals("abstract")) 260 { 261 if (ocType == null) 262 { 263 ocType = ObjectClassType.ABSTRACT; 264 } 265 else 266 { 267 throw new LDAPException(ResultCode.DECODING_ERROR, 268 ERR_OC_DECODE_MULTIPLE_OC_TYPES.get( 269 objectClassString)); 270 } 271 } 272 else if (lowerToken.equals("structural")) 273 { 274 if (ocType == null) 275 { 276 ocType = ObjectClassType.STRUCTURAL; 277 } 278 else 279 { 280 throw new LDAPException(ResultCode.DECODING_ERROR, 281 ERR_OC_DECODE_MULTIPLE_OC_TYPES.get( 282 objectClassString)); 283 } 284 } 285 else if (lowerToken.equals("auxiliary")) 286 { 287 if (ocType == null) 288 { 289 ocType = ObjectClassType.AUXILIARY; 290 } 291 else 292 { 293 throw new LDAPException(ResultCode.DECODING_ERROR, 294 ERR_OC_DECODE_MULTIPLE_OC_TYPES.get( 295 objectClassString)); 296 } 297 } 298 else if (lowerToken.equals("must")) 299 { 300 if (reqAttrs.isEmpty()) 301 { 302 pos = skipSpaces(objectClassString, pos, length); 303 pos = readOIDs(objectClassString, pos, length, token, reqAttrs); 304 } 305 else 306 { 307 throw new LDAPException(ResultCode.DECODING_ERROR, 308 ERR_OC_DECODE_MULTIPLE_ELEMENTS.get( 309 objectClassString, "MUST")); 310 } 311 } 312 else if (lowerToken.equals("may")) 313 { 314 if (optAttrs.isEmpty()) 315 { 316 pos = skipSpaces(objectClassString, pos, length); 317 pos = readOIDs(objectClassString, pos, length, token, optAttrs); 318 } 319 else 320 { 321 throw new LDAPException(ResultCode.DECODING_ERROR, 322 ERR_OC_DECODE_MULTIPLE_ELEMENTS.get( 323 objectClassString, "MAY")); 324 } 325 } 326 else if (lowerToken.startsWith("x-")) 327 { 328 pos = skipSpaces(objectClassString, pos, length); 329 330 final ArrayList<String> valueList = new ArrayList<>(5); 331 pos = readQDStrings(objectClassString, pos, length, token, valueList); 332 333 final String[] values = new String[valueList.size()]; 334 valueList.toArray(values); 335 336 if (exts.containsKey(token)) 337 { 338 throw new LDAPException(ResultCode.DECODING_ERROR, 339 ERR_OC_DECODE_DUP_EXT.get(objectClassString, 340 token)); 341 } 342 343 exts.put(token, values); 344 } 345 else 346 { 347 throw new LDAPException(ResultCode.DECODING_ERROR, 348 ERR_OC_DECODE_UNEXPECTED_TOKEN.get( 349 objectClassString, token)); 350 } 351 } 352 353 description = descr; 354 355 names = new String[nameList.size()]; 356 nameList.toArray(names); 357 358 superiorClasses = new String[supList.size()]; 359 supList.toArray(superiorClasses); 360 361 requiredAttributes = new String[reqAttrs.size()]; 362 reqAttrs.toArray(requiredAttributes); 363 364 optionalAttributes = new String[optAttrs.size()]; 365 optAttrs.toArray(optionalAttributes); 366 367 isObsolete = (obsolete != null); 368 369 objectClassType = ocType; 370 371 extensions = Collections.unmodifiableMap(exts); 372 } 373 374 375 376 /** 377 * Creates a new object class with the provided information. 378 * 379 * @param oid The OID for this object class. It must not be 380 * {@code null}. 381 * @param name The name for this object class. It may be 382 * {@code null} if the object class should only be 383 * referenced by OID. 384 * @param description The description for this object class. It may 385 * be {@code null} if there is no description. 386 * @param superiorClass The name/OID of the superior class for this 387 * object class. It may be {@code null} or 388 * empty if there is no superior class. 389 * @param objectClassType The object class type for this object class. 390 * @param requiredAttributes The names/OIDs of the attributes which must be 391 * present in entries containing this object 392 * class. 393 * @param optionalAttributes The names/OIDs of the attributes which may be 394 * present in entries containing this object 395 * class. 396 * @param extensions The set of extensions for this object class. 397 * It may be {@code null} or empty if there should 398 * not be any extensions. 399 */ 400 public ObjectClassDefinition(@NotNull final String oid, 401 @Nullable final String name, 402 @Nullable final String description, 403 @Nullable final String superiorClass, 404 @Nullable final ObjectClassType objectClassType, 405 @Nullable final String[] requiredAttributes, 406 @Nullable final String[] optionalAttributes, 407 @Nullable final Map<String,String[]> extensions) 408 { 409 this(oid, ((name == null) ? null : new String[] { name }), description, 410 false, 411 ((superiorClass == null) ? null : new String[] { superiorClass }), 412 objectClassType, requiredAttributes, optionalAttributes, 413 extensions); 414 } 415 416 417 418 /** 419 * Creates a new object class with the provided information. 420 * 421 * @param oid The OID for this object class. It must not be 422 * {@code null}. 423 * @param name The name for this object class. It may be 424 * {@code null} if the object class should only be 425 * referenced by OID. 426 * @param description The description for this object class. It may 427 * be {@code null} if there is no description. 428 * @param superiorClass The name/OID of the superior class for this 429 * object class. It may be {@code null} or 430 * empty if there is no superior class. 431 * @param objectClassType The object class type for this object class. 432 * @param requiredAttributes The names/OIDs of the attributes which must be 433 * present in entries containing this object 434 * class. 435 * @param optionalAttributes The names/OIDs of the attributes which may be 436 * present in entries containing this object 437 * class. 438 * @param extensions The set of extensions for this object class. 439 * It may be {@code null} or empty if there should 440 * not be any extensions. 441 */ 442 public ObjectClassDefinition(@NotNull final String oid, 443 @Nullable final String name, 444 @Nullable final String description, 445 @Nullable final String superiorClass, 446 @Nullable final ObjectClassType objectClassType, 447 @Nullable final Collection<String> requiredAttributes, 448 @Nullable final Collection<String> optionalAttributes, 449 @Nullable final Map<String,String[]> extensions) 450 { 451 this(oid, ((name == null) ? null : new String[] { name }), description, 452 false, 453 ((superiorClass == null) ? null : new String[] { superiorClass }), 454 objectClassType, toArray(requiredAttributes), 455 toArray(optionalAttributes), extensions); 456 } 457 458 459 460 /** 461 * Creates a new object class with the provided information. 462 * 463 * @param oid The OID for this object class. It must not be 464 * {@code null}. 465 * @param names The set of names for this object class. It may 466 * be {@code null} or empty if the object class 467 * should only be referenced by OID. 468 * @param description The description for this object class. It may 469 * be {@code null} if there is no description. 470 * @param isObsolete Indicates whether this object class is declared 471 * obsolete. 472 * @param superiorClasses The names/OIDs of the superior classes for this 473 * object class. It may be {@code null} or 474 * empty if there is no superior class. 475 * @param objectClassType The object class type for this object class. 476 * @param requiredAttributes The names/OIDs of the attributes which must be 477 * present in entries containing this object 478 * class. 479 * @param optionalAttributes The names/OIDs of the attributes which may be 480 * present in entries containing this object 481 * class. 482 * @param extensions The set of extensions for this object class. 483 * It may be {@code null} or empty if there should 484 * not be any extensions. 485 */ 486 public ObjectClassDefinition(@NotNull final String oid, 487 @Nullable final String[] names, 488 @Nullable final String description, 489 final boolean isObsolete, 490 @Nullable final String[] superiorClasses, 491 @Nullable final ObjectClassType objectClassType, 492 @Nullable final String[] requiredAttributes, 493 @Nullable final String[] optionalAttributes, 494 @Nullable final Map<String,String[]> extensions) 495 { 496 Validator.ensureNotNull(oid); 497 498 this.oid = oid; 499 this.isObsolete = isObsolete; 500 this.description = description; 501 this.objectClassType = objectClassType; 502 503 if (names == null) 504 { 505 this.names = StaticUtils.NO_STRINGS; 506 } 507 else 508 { 509 this.names = names; 510 } 511 512 if (superiorClasses == null) 513 { 514 this.superiorClasses = StaticUtils.NO_STRINGS; 515 } 516 else 517 { 518 this.superiorClasses = superiorClasses; 519 } 520 521 if (requiredAttributes == null) 522 { 523 this.requiredAttributes = StaticUtils.NO_STRINGS; 524 } 525 else 526 { 527 this.requiredAttributes = requiredAttributes; 528 } 529 530 if (optionalAttributes == null) 531 { 532 this.optionalAttributes = StaticUtils.NO_STRINGS; 533 } 534 else 535 { 536 this.optionalAttributes = optionalAttributes; 537 } 538 539 if (extensions == null) 540 { 541 this.extensions = Collections.emptyMap(); 542 } 543 else 544 { 545 this.extensions = Collections.unmodifiableMap(extensions); 546 } 547 548 final StringBuilder buffer = new StringBuilder(); 549 createDefinitionString(buffer); 550 objectClassString = buffer.toString(); 551 } 552 553 554 555 /** 556 * Constructs a string representation of this object class definition in the 557 * provided buffer. 558 * 559 * @param buffer The buffer in which to construct a string representation of 560 * this object class definition. 561 */ 562 private void createDefinitionString(@NotNull final StringBuilder buffer) 563 { 564 buffer.append("( "); 565 buffer.append(oid); 566 567 if (names.length == 1) 568 { 569 buffer.append(" NAME '"); 570 buffer.append(names[0]); 571 buffer.append('\''); 572 } 573 else if (names.length > 1) 574 { 575 buffer.append(" NAME ("); 576 for (final String name : names) 577 { 578 buffer.append(" '"); 579 buffer.append(name); 580 buffer.append('\''); 581 } 582 buffer.append(" )"); 583 } 584 585 if (description != null) 586 { 587 buffer.append(" DESC '"); 588 encodeValue(description, buffer); 589 buffer.append('\''); 590 } 591 592 if (isObsolete) 593 { 594 buffer.append(" OBSOLETE"); 595 } 596 597 if (superiorClasses.length == 1) 598 { 599 buffer.append(" SUP "); 600 buffer.append(superiorClasses[0]); 601 } 602 else if (superiorClasses.length > 1) 603 { 604 buffer.append(" SUP ("); 605 for (int i=0; i < superiorClasses.length; i++) 606 { 607 if (i > 0) 608 { 609 buffer.append(" $ "); 610 } 611 else 612 { 613 buffer.append(' '); 614 } 615 buffer.append(superiorClasses[i]); 616 } 617 buffer.append(" )"); 618 } 619 620 if (objectClassType != null) 621 { 622 buffer.append(' '); 623 buffer.append(objectClassType.getName()); 624 } 625 626 if (requiredAttributes.length == 1) 627 { 628 buffer.append(" MUST "); 629 buffer.append(requiredAttributes[0]); 630 } 631 else if (requiredAttributes.length > 1) 632 { 633 buffer.append(" MUST ("); 634 for (int i=0; i < requiredAttributes.length; i++) 635 { 636 if (i >0) 637 { 638 buffer.append(" $ "); 639 } 640 else 641 { 642 buffer.append(' '); 643 } 644 buffer.append(requiredAttributes[i]); 645 } 646 buffer.append(" )"); 647 } 648 649 if (optionalAttributes.length == 1) 650 { 651 buffer.append(" MAY "); 652 buffer.append(optionalAttributes[0]); 653 } 654 else if (optionalAttributes.length > 1) 655 { 656 buffer.append(" MAY ("); 657 for (int i=0; i < optionalAttributes.length; i++) 658 { 659 if (i > 0) 660 { 661 buffer.append(" $ "); 662 } 663 else 664 { 665 buffer.append(' '); 666 } 667 buffer.append(optionalAttributes[i]); 668 } 669 buffer.append(" )"); 670 } 671 672 for (final Map.Entry<String,String[]> e : extensions.entrySet()) 673 { 674 final String name = e.getKey(); 675 final String[] values = e.getValue(); 676 if (values.length == 1) 677 { 678 buffer.append(' '); 679 buffer.append(name); 680 buffer.append(" '"); 681 encodeValue(values[0], buffer); 682 buffer.append('\''); 683 } 684 else 685 { 686 buffer.append(' '); 687 buffer.append(name); 688 buffer.append(" ("); 689 for (final String value : values) 690 { 691 buffer.append(" '"); 692 encodeValue(value, buffer); 693 buffer.append('\''); 694 } 695 buffer.append(" )"); 696 } 697 } 698 699 buffer.append(" )"); 700 } 701 702 703 704 /** 705 * Retrieves the OID for this object class. 706 * 707 * @return The OID for this object class. 708 */ 709 @NotNull() 710 public String getOID() 711 { 712 return oid; 713 } 714 715 716 717 /** 718 * Retrieves the set of names for this object class. 719 * 720 * @return The set of names for this object class, or an empty array if it 721 * does not have any names. 722 */ 723 @NotNull() 724 public String[] getNames() 725 { 726 return names; 727 } 728 729 730 731 /** 732 * Retrieves the primary name that can be used to reference this object 733 * class. If one or more names are defined, then the first name will be used. 734 * Otherwise, the OID will be returned. 735 * 736 * @return The primary name that can be used to reference this object class. 737 */ 738 @NotNull() 739 public String getNameOrOID() 740 { 741 if (names.length == 0) 742 { 743 return oid; 744 } 745 else 746 { 747 return names[0]; 748 } 749 } 750 751 752 753 /** 754 * Indicates whether the provided string matches the OID or any of the names 755 * for this object class. 756 * 757 * @param s The string for which to make the determination. It must not be 758 * {@code null}. 759 * 760 * @return {@code true} if the provided string matches the OID or any of the 761 * names for this object class, or {@code false} if not. 762 */ 763 public boolean hasNameOrOID(@NotNull final String s) 764 { 765 for (final String name : names) 766 { 767 if (s.equalsIgnoreCase(name)) 768 { 769 return true; 770 } 771 } 772 773 return s.equalsIgnoreCase(oid); 774 } 775 776 777 778 /** 779 * Retrieves the description for this object class, if available. 780 * 781 * @return The description for this object class, or {@code null} if there is 782 * no description defined. 783 */ 784 @Nullable() 785 public String getDescription() 786 { 787 return description; 788 } 789 790 791 792 /** 793 * Indicates whether this object class is declared obsolete. 794 * 795 * @return {@code true} if this object class is declared obsolete, or 796 * {@code false} if it is not. 797 */ 798 public boolean isObsolete() 799 { 800 return isObsolete; 801 } 802 803 804 805 /** 806 * Retrieves the names or OIDs of the superior classes for this object class, 807 * if available. 808 * 809 * @return The names or OIDs of the superior classes for this object class, 810 * or an empty array if it does not have any superior classes. 811 */ 812 @NotNull() 813 public String[] getSuperiorClasses() 814 { 815 return superiorClasses; 816 } 817 818 819 820 /** 821 * Retrieves the object class definitions for the superior object classes. 822 * 823 * @param schema The schema to use to retrieve the object class 824 * definitions. 825 * @param recursive Indicates whether to recursively include all of the 826 * superior object class definitions from superior classes. 827 * 828 * @return The object class definitions for the superior object classes. 829 */ 830 @NotNull() 831 public Set<ObjectClassDefinition> getSuperiorClasses( 832 @NotNull final Schema schema, final boolean recursive) 833 { 834 final LinkedHashSet<ObjectClassDefinition> ocSet = 835 new LinkedHashSet<>(StaticUtils.computeMapCapacity(10)); 836 for (final String s : superiorClasses) 837 { 838 final ObjectClassDefinition d = schema.getObjectClass(s); 839 if (d != null) 840 { 841 ocSet.add(d); 842 if (recursive) 843 { 844 getSuperiorClasses(schema, d, ocSet); 845 } 846 } 847 } 848 849 return Collections.unmodifiableSet(ocSet); 850 } 851 852 853 854 /** 855 * Recursively adds superior class definitions to the provided set. 856 * 857 * @param schema The schema to use to retrieve the object class definitions. 858 * @param oc The object class definition to be processed. 859 * @param ocSet The set to which the definitions should be added. 860 */ 861 private static void getSuperiorClasses(@NotNull final Schema schema, 862 @NotNull final ObjectClassDefinition oc, 863 @NotNull final Set<ObjectClassDefinition> ocSet) 864 { 865 for (final String s : oc.superiorClasses) 866 { 867 final ObjectClassDefinition d = schema.getObjectClass(s); 868 if (d != null) 869 { 870 ocSet.add(d); 871 getSuperiorClasses(schema, d, ocSet); 872 } 873 } 874 } 875 876 877 878 /** 879 * Retrieves the object class type for this object class. This method will 880 * return {@code null} if this object class definition does not explicitly 881 * specify the object class type, although in that case, the object class type 882 * should be assumed to be {@link ObjectClassType#STRUCTURAL} as per RFC 4512 883 * section 4.1.1. 884 * 885 * @return The object class type for this object class, or {@code null} if it 886 * is not defined in the schema element. 887 */ 888 @Nullable() 889 public ObjectClassType getObjectClassType() 890 { 891 return objectClassType; 892 } 893 894 895 896 /** 897 * Retrieves the object class type for this object class, recursively 898 * examining superior classes if necessary to make the determination. 899 * <BR><BR> 900 * Note that versions of this method before the 4.0.6 release of the LDAP SDK 901 * operated under the incorrect assumption that if an object class definition 902 * did not explicitly specify the object class type, it would be inherited 903 * from its superclass. The correct behavior, as per RFC 4512 section 4.1.1, 904 * is that if the object class type is not explicitly specified, it should be 905 * assumed to be {@link ObjectClassType#STRUCTURAL}. 906 * 907 * @param schema The schema to use to retrieve the definitions for the 908 * superior object classes. As of LDAP SDK version 4.0.6, 909 * this argument is no longer used (and may be {@code null} if 910 * desired), but this version of the method has been preserved 911 * both for the purpose of retaining API compatibility with 912 * previous versions of the LDAP SDK, and to disambiguate it 913 * from the zero-argument version of the method that returns 914 * {@code null} if the object class type is not explicitly 915 * specified. 916 * 917 * @return The object class type for this object class, or 918 * {@link ObjectClassType#STRUCTURAL} if it is not explicitly 919 * defined. 920 */ 921 @NotNull() 922 public ObjectClassType getObjectClassType(@NotNull final Schema schema) 923 { 924 if (objectClassType == null) 925 { 926 return ObjectClassType.STRUCTURAL; 927 } 928 else 929 { 930 return objectClassType; 931 } 932 } 933 934 935 936 /** 937 * Retrieves the names or OIDs of the attributes that are required to be 938 * present in entries containing this object class. Note that this will not 939 * automatically include the set of required attributes from any superior 940 * classes. 941 * 942 * @return The names or OIDs of the attributes that are required to be 943 * present in entries containing this object class, or an empty array 944 * if there are no required attributes. 945 */ 946 @NotNull() 947 public String[] getRequiredAttributes() 948 { 949 return requiredAttributes; 950 } 951 952 953 954 /** 955 * Retrieves the attribute type definitions for the attributes that are 956 * required to be present in entries containing this object class, optionally 957 * including the set of required attribute types from superior classes. 958 * 959 * @param schema The schema to use to retrieve the 960 * attribute type definitions. 961 * @param includeSuperiorClasses Indicates whether to include definitions 962 * for required attribute types in superior 963 * object classes. 964 * 965 * @return The attribute type definitions for the attributes that are 966 * required to be present in entries containing this object class. 967 */ 968 @NotNull() 969 public Set<AttributeTypeDefinition> getRequiredAttributes( 970 @NotNull final Schema schema, 971 final boolean includeSuperiorClasses) 972 { 973 final HashSet<AttributeTypeDefinition> attrSet = 974 new HashSet<>(StaticUtils.computeMapCapacity(20)); 975 for (final String s : requiredAttributes) 976 { 977 final AttributeTypeDefinition d = schema.getAttributeType(s); 978 if (d != null) 979 { 980 attrSet.add(d); 981 } 982 } 983 984 if (includeSuperiorClasses) 985 { 986 for (final String s : superiorClasses) 987 { 988 final ObjectClassDefinition d = schema.getObjectClass(s); 989 if (d != null) 990 { 991 getSuperiorRequiredAttributes(schema, d, attrSet); 992 } 993 } 994 } 995 996 return Collections.unmodifiableSet(attrSet); 997 } 998 999 1000 1001 /** 1002 * Recursively adds the required attributes from the provided object class 1003 * to the given set. 1004 * 1005 * @param schema The schema to use during processing. 1006 * @param oc The object class to be processed. 1007 * @param attrSet The set to which the attribute type definitions should be 1008 * added. 1009 */ 1010 private static void getSuperiorRequiredAttributes( 1011 @NotNull final Schema schema, 1012 @NotNull final ObjectClassDefinition oc, 1013 @NotNull final Set<AttributeTypeDefinition> attrSet) 1014 { 1015 for (final String s : oc.requiredAttributes) 1016 { 1017 final AttributeTypeDefinition d = schema.getAttributeType(s); 1018 if (d != null) 1019 { 1020 attrSet.add(d); 1021 } 1022 } 1023 1024 for (final String s : oc.superiorClasses) 1025 { 1026 final ObjectClassDefinition d = schema.getObjectClass(s); 1027 if (d != null) 1028 { 1029 getSuperiorRequiredAttributes(schema, d, attrSet); 1030 } 1031 } 1032 } 1033 1034 1035 1036 /** 1037 * Retrieves the names or OIDs of the attributes that may optionally be 1038 * present in entries containing this object class. Note that this will not 1039 * automatically include the set of optional attributes from any superior 1040 * classes. 1041 * 1042 * @return The names or OIDs of the attributes that may optionally be present 1043 * in entries containing this object class, or an empty array if 1044 * there are no optional attributes. 1045 */ 1046 @NotNull() 1047 public String[] getOptionalAttributes() 1048 { 1049 return optionalAttributes; 1050 } 1051 1052 1053 1054 /** 1055 * Retrieves the attribute type definitions for the attributes that may 1056 * optionally be present in entries containing this object class, optionally 1057 * including the set of optional attribute types from superior classes. 1058 * 1059 * @param schema The schema to use to retrieve the 1060 * attribute type definitions. 1061 * @param includeSuperiorClasses Indicates whether to include definitions 1062 * for optional attribute types in superior 1063 * object classes. 1064 * 1065 * @return The attribute type definitions for the attributes that may 1066 * optionally be present in entries containing this object class. 1067 */ 1068 @NotNull() 1069 public Set<AttributeTypeDefinition> getOptionalAttributes( 1070 @NotNull final Schema schema, 1071 final boolean includeSuperiorClasses) 1072 { 1073 final HashSet<AttributeTypeDefinition> attrSet = 1074 new HashSet<>(StaticUtils.computeMapCapacity(20)); 1075 for (final String s : optionalAttributes) 1076 { 1077 final AttributeTypeDefinition d = schema.getAttributeType(s); 1078 if (d != null) 1079 { 1080 attrSet.add(d); 1081 } 1082 } 1083 1084 if (includeSuperiorClasses) 1085 { 1086 final Set<AttributeTypeDefinition> requiredAttrs = 1087 getRequiredAttributes(schema, true); 1088 for (final AttributeTypeDefinition d : requiredAttrs) 1089 { 1090 attrSet.remove(d); 1091 } 1092 1093 for (final String s : superiorClasses) 1094 { 1095 final ObjectClassDefinition d = schema.getObjectClass(s); 1096 if (d != null) 1097 { 1098 getSuperiorOptionalAttributes(schema, d, attrSet, requiredAttrs); 1099 } 1100 } 1101 } 1102 1103 return Collections.unmodifiableSet(attrSet); 1104 } 1105 1106 1107 1108 /** 1109 * Recursively adds the optional attributes from the provided object class 1110 * to the given set. 1111 * 1112 * @param schema The schema to use during processing. 1113 * @param oc The object class to be processed. 1114 * @param attrSet The set to which the attribute type definitions should 1115 * be added. 1116 * @param requiredSet x 1117 */ 1118 private static void getSuperiorOptionalAttributes( 1119 @NotNull final Schema schema, 1120 @NotNull final ObjectClassDefinition oc, 1121 @NotNull final Set<AttributeTypeDefinition> attrSet, 1122 @NotNull final Set<AttributeTypeDefinition> requiredSet) 1123 { 1124 for (final String s : oc.optionalAttributes) 1125 { 1126 final AttributeTypeDefinition d = schema.getAttributeType(s); 1127 if ((d != null) && (! requiredSet.contains(d))) 1128 { 1129 attrSet.add(d); 1130 } 1131 } 1132 1133 for (final String s : oc.superiorClasses) 1134 { 1135 final ObjectClassDefinition d = schema.getObjectClass(s); 1136 if (d != null) 1137 { 1138 getSuperiorOptionalAttributes(schema, d, attrSet, requiredSet); 1139 } 1140 } 1141 } 1142 1143 1144 1145 /** 1146 * Retrieves the set of extensions for this object class. They will be mapped 1147 * from the extension name (which should start with "X-") to the set of values 1148 * for that extension. 1149 * 1150 * @return The set of extensions for this object class. 1151 */ 1152 @NotNull() 1153 public Map<String,String[]> getExtensions() 1154 { 1155 return extensions; 1156 } 1157 1158 1159 1160 /** 1161 * {@inheritDoc} 1162 */ 1163 @Override() 1164 @NotNull() 1165 public SchemaElementType getSchemaElementType() 1166 { 1167 return SchemaElementType.OBJECT_CLASS; 1168 } 1169 1170 1171 1172 /** 1173 * {@inheritDoc} 1174 */ 1175 @Override() 1176 public int hashCode() 1177 { 1178 return oid.hashCode(); 1179 } 1180 1181 1182 1183 /** 1184 * {@inheritDoc} 1185 */ 1186 @Override() 1187 public boolean equals(@Nullable final Object o) 1188 { 1189 if (o == null) 1190 { 1191 return false; 1192 } 1193 1194 if (o == this) 1195 { 1196 return true; 1197 } 1198 1199 if (! (o instanceof ObjectClassDefinition)) 1200 { 1201 return false; 1202 } 1203 1204 final ObjectClassDefinition d = (ObjectClassDefinition) o; 1205 return (oid.equals(d.oid) && 1206 StaticUtils.stringsEqualIgnoreCaseOrderIndependent(names, d.names) && 1207 StaticUtils.stringsEqualIgnoreCaseOrderIndependent(requiredAttributes, 1208 d.requiredAttributes) && 1209 StaticUtils.stringsEqualIgnoreCaseOrderIndependent(optionalAttributes, 1210 d.optionalAttributes) && 1211 StaticUtils.stringsEqualIgnoreCaseOrderIndependent(superiorClasses, 1212 d.superiorClasses) && 1213 StaticUtils.bothNullOrEqual(objectClassType, d.objectClassType) && 1214 StaticUtils.bothNullOrEqualIgnoreCase(description, d.description) && 1215 (isObsolete == d.isObsolete) && 1216 extensionsEqual(extensions, d.extensions)); 1217 } 1218 1219 1220 1221 /** 1222 * Retrieves a string representation of this object class definition, in the 1223 * format described in RFC 4512 section 4.1.1. 1224 * 1225 * @return A string representation of this object class definition. 1226 */ 1227 @Override() 1228 @NotNull() 1229 public String toString() 1230 { 1231 return objectClassString; 1232 } 1233}