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 }