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