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