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