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