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