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 */
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 // 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 }