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    }