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