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