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