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    }