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