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