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