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