001/*
002 * Copyright 2007-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2014 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      getSuperiorRequiredAttributes(schema, d, attrSet);
899    }
900  }
901
902
903
904  /**
905   * Retrieves the names or OIDs of the attributes that may optionally be
906   * present in entries containing this object class.  Note that this will not
907   * automatically include the set of optional attributes from any superior
908   * classes.
909   *
910   * @return  The names or OIDs of the attributes that may optionally be present
911   *          in entries containing this object class, or an empty array if
912   *          there are no optional attributes.
913   */
914  public String[] getOptionalAttributes()
915  {
916    return optionalAttributes;
917  }
918
919
920
921  /**
922   * Retrieves the attribute type definitions for the attributes that may
923   * optionally be present in entries containing this object class, optionally
924   * including the set of optional attribute types from superior classes.
925   *
926   * @param  schema                  The schema to use to retrieve the
927   *                                 attribute type definitions.
928   * @param  includeSuperiorClasses  Indicates whether to include definitions
929   *                                 for optional attribute types in superior
930   *                                 object classes.
931   *
932   * @return  The attribute type definitions for the attributes that may
933   *          optionally be present in entries containing this object class.
934   */
935  public Set<AttributeTypeDefinition> getOptionalAttributes(final Schema schema,
936                                           final boolean includeSuperiorClasses)
937  {
938    final HashSet<AttributeTypeDefinition> attrSet =
939         new HashSet<AttributeTypeDefinition>();
940    for (final String s : optionalAttributes)
941    {
942      final AttributeTypeDefinition d = schema.getAttributeType(s);
943      if (d != null)
944      {
945        attrSet.add(d);
946      }
947    }
948
949    if (includeSuperiorClasses)
950    {
951      final Set<AttributeTypeDefinition> requiredAttrs =
952           getRequiredAttributes(schema, true);
953      for (final AttributeTypeDefinition d : requiredAttrs)
954      {
955        attrSet.remove(d);
956      }
957
958      for (final String s : superiorClasses)
959      {
960        final ObjectClassDefinition d = schema.getObjectClass(s);
961        if (d != null)
962        {
963          getSuperiorOptionalAttributes(schema, d, attrSet, requiredAttrs);
964        }
965      }
966    }
967
968    return Collections.unmodifiableSet(attrSet);
969  }
970
971
972
973  /**
974   * Recursively adds the optional attributes from the provided object class
975   * to the given set.
976   *
977   * @param  schema       The schema to use during processing.
978   * @param  oc           The object class to be processed.
979   * @param  attrSet      The set to which the attribute type definitions should
980   *                      be added.
981   * @param  requiredSet  x
982   */
983  private static void getSuperiorOptionalAttributes(final Schema schema,
984                           final ObjectClassDefinition oc,
985                           final Set<AttributeTypeDefinition> attrSet,
986                           final Set<AttributeTypeDefinition> requiredSet)
987  {
988    for (final String s : oc.optionalAttributes)
989    {
990      final AttributeTypeDefinition d = schema.getAttributeType(s);
991      if ((d != null) && (! requiredSet.contains(d)))
992      {
993        attrSet.add(d);
994      }
995    }
996
997    for (final String s : oc.superiorClasses)
998    {
999      final ObjectClassDefinition d = schema.getObjectClass(s);
1000      getSuperiorOptionalAttributes(schema, d, attrSet, requiredSet);
1001    }
1002  }
1003
1004
1005
1006  /**
1007   * Retrieves the set of extensions for this object class.  They will be mapped
1008   * from the extension name (which should start with "X-") to the set of values
1009   * for that extension.
1010   *
1011   * @return  The set of extensions for this object class.
1012   */
1013  public Map<String,String[]> getExtensions()
1014  {
1015    return extensions;
1016  }
1017
1018
1019
1020  /**
1021   * {@inheritDoc}
1022   */
1023  @Override()
1024  public int hashCode()
1025  {
1026    return oid.hashCode();
1027  }
1028
1029
1030
1031  /**
1032   * {@inheritDoc}
1033   */
1034  @Override()
1035  public boolean equals(final Object o)
1036  {
1037    if (o == null)
1038    {
1039      return false;
1040    }
1041
1042    if (o == this)
1043    {
1044      return true;
1045    }
1046
1047    if (! (o instanceof ObjectClassDefinition))
1048    {
1049      return false;
1050    }
1051
1052    final ObjectClassDefinition d = (ObjectClassDefinition) o;
1053    return (oid.equals(d.oid) &&
1054         stringsEqualIgnoreCaseOrderIndependent(names, d.names) &&
1055         stringsEqualIgnoreCaseOrderIndependent(requiredAttributes,
1056              d.requiredAttributes) &&
1057         stringsEqualIgnoreCaseOrderIndependent(optionalAttributes,
1058              d.optionalAttributes) &&
1059         stringsEqualIgnoreCaseOrderIndependent(superiorClasses,
1060              d.superiorClasses) &&
1061         bothNullOrEqual(objectClassType, d.objectClassType) &&
1062         bothNullOrEqualIgnoreCase(description, d.description) &&
1063         (isObsolete == d.isObsolete) &&
1064         extensionsEqual(extensions, d.extensions));
1065  }
1066
1067
1068
1069  /**
1070   * Retrieves a string representation of this object class definition, in the
1071   * format described in RFC 4512 section 4.1.1.
1072   *
1073   * @return  A string representation of this object class definition.
1074   */
1075  @Override()
1076  public String toString()
1077  {
1078    return objectClassString;
1079  }
1080}