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