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