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.Collections;
042import java.util.Map;
043import java.util.LinkedHashMap;
044
045import com.unboundid.ldap.sdk.LDAPException;
046import com.unboundid.ldap.sdk.ResultCode;
047import com.unboundid.util.Debug;
048import com.unboundid.util.NotMutable;
049import com.unboundid.util.NotNull;
050import com.unboundid.util.Nullable;
051import com.unboundid.util.StaticUtils;
052import com.unboundid.util.ThreadSafety;
053import com.unboundid.util.ThreadSafetyLevel;
054import com.unboundid.util.Validator;
055
056import static com.unboundid.ldap.sdk.schema.SchemaMessages.*;
057
058
059
060/**
061 * This class provides a data structure that describes an LDAP attribute type
062 * schema element.
063 */
064@NotMutable()
065@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
066public final class AttributeTypeDefinition
067       extends SchemaElement
068{
069  /**
070   * The serial version UID for this serializable class.
071   */
072  private static final long serialVersionUID = -6688185196734362719L;
073
074
075
076  // The usage for this attribute type.
077  @NotNull private final AttributeUsage usage;
078
079  // Indicates whether this attribute type is declared collective.
080  private final boolean isCollective;
081
082  // Indicates whether this attribute type is declared no-user-modification.
083  private final boolean isNoUserModification;
084
085  // Indicates whether this attribute type is declared obsolete.
086  private final boolean isObsolete;
087
088  // Indicates whether this attribute type is declared single-valued.
089  private final boolean isSingleValued;
090
091  // The set of extensions for this attribute type.
092  @NotNull private final Map<String,String[]> extensions;
093
094  // The string representation of this attribute type.
095  @NotNull private final String attributeTypeString;
096
097  // The description for this attribute type.
098  @Nullable private final String description;
099
100  // The name/OID of the equality matching rule for this attribute type.
101  @Nullable private final String equalityMatchingRule;
102
103  // The OID for this attribute type.
104  @NotNull private final String oid;
105
106  // The name/OID of the ordering matching rule for this attribute type.
107  @Nullable private final String orderingMatchingRule;
108
109  // The name/OID of the substring matching rule for this attribute type.
110  @Nullable private final String substringMatchingRule;
111
112  // The name of the superior type for this attribute type.
113  @Nullable private final String superiorType;
114
115  // The OID of the syntax for this attribute type.
116  @Nullable private final String syntaxOID;
117
118  // The set of names for this attribute type.
119  @NotNull private final String[] names;
120
121
122
123  /**
124   * Creates a new attribute type from the provided string representation.
125   *
126   * @param  s  The string representation of the attribute type to create, using
127   *            the syntax described in RFC 4512 section 4.1.2.  It must not be
128   *            {@code null}.
129   *
130   * @throws  LDAPException  If the provided string cannot be decoded as an
131   *                         attribute type definition.
132   */
133  public AttributeTypeDefinition(@NotNull final String s)
134         throws LDAPException
135  {
136    Validator.ensureNotNull(s);
137
138    attributeTypeString = s.trim();
139
140    // The first character must be an opening parenthesis.
141    final int length = attributeTypeString.length();
142    if (length == 0)
143    {
144      throw new LDAPException(ResultCode.DECODING_ERROR,
145                              ERR_ATTRTYPE_DECODE_EMPTY.get());
146    }
147    else if (attributeTypeString.charAt(0) != '(')
148    {
149      throw new LDAPException(ResultCode.DECODING_ERROR,
150                              ERR_ATTRTYPE_DECODE_NO_OPENING_PAREN.get(
151                                   attributeTypeString));
152    }
153
154
155    // Skip over any spaces until we reach the start of the OID, then read the
156    // OID until we find the next space.
157    int pos = skipSpaces(attributeTypeString, 1, length);
158
159    StringBuilder buffer = new StringBuilder();
160    pos = readOID(attributeTypeString, pos, length, buffer);
161    oid = buffer.toString();
162
163
164    // Technically, attribute type elements are supposed to appear in a specific
165    // order, but we'll be lenient and allow remaining elements to come in any
166    // order.
167    final ArrayList<String> nameList = new ArrayList<>(1);
168    AttributeUsage attrUsage = null;
169    Boolean collective = null;
170    Boolean noUserMod = null;
171    Boolean obsolete = null;
172    Boolean singleValue = null;
173    final Map<String,String[]> exts  =
174         new LinkedHashMap<>(StaticUtils.computeMapCapacity(5));
175    String descr = null;
176    String eqRule = null;
177    String ordRule = null;
178    String subRule = null;
179    String supType = null;
180    String synOID = null;
181
182    while (true)
183    {
184      // Skip over any spaces until we find the next element.
185      pos = skipSpaces(attributeTypeString, pos, length);
186
187      // Read until we find the next space or the end of the string.  Use that
188      // token to figure out what to do next.
189      final int tokenStartPos = pos;
190      while ((pos < length) && (attributeTypeString.charAt(pos) != ' '))
191      {
192        pos++;
193      }
194
195      String token = attributeTypeString.substring(tokenStartPos, pos);
196
197      // It's possible that the token could be smashed right up against the
198      // closing parenthesis.  If that's the case, then extract just the token
199      // and handle the closing parenthesis the next time through.
200      if ((token.length() > 1) && (token.endsWith(")")))
201      {
202        token = token.substring(0, token.length() - 1);
203        pos--;
204      }
205
206      final String lowerToken = StaticUtils.toLowerCase(token);
207      if (lowerToken.equals(")"))
208      {
209        // This indicates that we're at the end of the value.  There should not
210        // be any more closing characters.
211        if (pos < length)
212        {
213          throw new LDAPException(ResultCode.DECODING_ERROR,
214                                  ERR_ATTRTYPE_DECODE_CLOSE_NOT_AT_END.get(
215                                       attributeTypeString));
216        }
217        break;
218      }
219      else if (lowerToken.equals("name"))
220      {
221        if (nameList.isEmpty())
222        {
223          pos = skipSpaces(attributeTypeString, pos, length);
224          pos = readQDStrings(attributeTypeString, pos, length, token,
225               nameList);
226        }
227        else
228        {
229          throw new LDAPException(ResultCode.DECODING_ERROR,
230                                  ERR_ATTRTYPE_DECODE_MULTIPLE_ELEMENTS.get(
231                                       attributeTypeString, "NAME"));
232        }
233      }
234      else if (lowerToken.equals("desc"))
235      {
236        if (descr == null)
237        {
238          pos = skipSpaces(attributeTypeString, pos, length);
239
240          buffer = new StringBuilder();
241          pos = readQDString(attributeTypeString, pos, length, token, buffer);
242          descr = buffer.toString();
243        }
244        else
245        {
246          throw new LDAPException(ResultCode.DECODING_ERROR,
247                                  ERR_ATTRTYPE_DECODE_MULTIPLE_ELEMENTS.get(
248                                       attributeTypeString, "DESC"));
249        }
250      }
251      else if (lowerToken.equals("obsolete"))
252      {
253        if (obsolete == null)
254        {
255          obsolete = true;
256        }
257        else
258        {
259          throw new LDAPException(ResultCode.DECODING_ERROR,
260                                  ERR_ATTRTYPE_DECODE_MULTIPLE_ELEMENTS.get(
261                                       attributeTypeString, "OBSOLETE"));
262        }
263      }
264      else if (lowerToken.equals("sup"))
265      {
266        if (supType == null)
267        {
268          pos = skipSpaces(attributeTypeString, pos, length);
269
270          buffer = new StringBuilder();
271          pos = readOID(attributeTypeString, pos, length, buffer);
272          supType = buffer.toString();
273        }
274        else
275        {
276          throw new LDAPException(ResultCode.DECODING_ERROR,
277                                  ERR_ATTRTYPE_DECODE_MULTIPLE_ELEMENTS.get(
278                                       attributeTypeString, "SUP"));
279        }
280      }
281      else if (lowerToken.equals("equality"))
282      {
283        if (eqRule == null)
284        {
285          pos = skipSpaces(attributeTypeString, pos, length);
286
287          buffer = new StringBuilder();
288          pos = readOID(attributeTypeString, pos, length, buffer);
289          eqRule = buffer.toString();
290        }
291        else
292        {
293          throw new LDAPException(ResultCode.DECODING_ERROR,
294                                  ERR_ATTRTYPE_DECODE_MULTIPLE_ELEMENTS.get(
295                                       attributeTypeString, "EQUALITY"));
296        }
297      }
298      else if (lowerToken.equals("ordering"))
299      {
300        if (ordRule == null)
301        {
302          pos = skipSpaces(attributeTypeString, pos, length);
303
304          buffer = new StringBuilder();
305          pos = readOID(attributeTypeString, pos, length, buffer);
306          ordRule = buffer.toString();
307        }
308        else
309        {
310          throw new LDAPException(ResultCode.DECODING_ERROR,
311                                  ERR_ATTRTYPE_DECODE_MULTIPLE_ELEMENTS.get(
312                                       attributeTypeString, "ORDERING"));
313        }
314      }
315      else if (lowerToken.equals("substr"))
316      {
317        if (subRule == null)
318        {
319          pos = skipSpaces(attributeTypeString, pos, length);
320
321          buffer = new StringBuilder();
322          pos = readOID(attributeTypeString, pos, length, buffer);
323          subRule = buffer.toString();
324        }
325        else
326        {
327          throw new LDAPException(ResultCode.DECODING_ERROR,
328                                  ERR_ATTRTYPE_DECODE_MULTIPLE_ELEMENTS.get(
329                                       attributeTypeString, "SUBSTR"));
330        }
331      }
332      else if (lowerToken.equals("syntax"))
333      {
334        if (synOID == null)
335        {
336          pos = skipSpaces(attributeTypeString, pos, length);
337
338          buffer = new StringBuilder();
339          pos = readOID(attributeTypeString, pos, length, buffer);
340          synOID = buffer.toString();
341        }
342        else
343        {
344          throw new LDAPException(ResultCode.DECODING_ERROR,
345                                  ERR_ATTRTYPE_DECODE_MULTIPLE_ELEMENTS.get(
346                                       attributeTypeString, "SYNTAX"));
347        }
348      }
349      else if (lowerToken.equals("single-value"))
350      {
351        if (singleValue == null)
352        {
353          singleValue = true;
354        }
355        else
356        {
357          throw new LDAPException(ResultCode.DECODING_ERROR,
358                                  ERR_ATTRTYPE_DECODE_MULTIPLE_ELEMENTS.get(
359                                       attributeTypeString, "SINGLE-VALUE"));
360        }
361      }
362      else if (lowerToken.equals("collective"))
363      {
364        if (collective == null)
365        {
366          collective = true;
367        }
368        else
369        {
370          throw new LDAPException(ResultCode.DECODING_ERROR,
371                                  ERR_ATTRTYPE_DECODE_MULTIPLE_ELEMENTS.get(
372                                       attributeTypeString, "COLLECTIVE"));
373        }
374      }
375      else if (lowerToken.equals("no-user-modification"))
376      {
377        if (noUserMod == null)
378        {
379          noUserMod = true;
380        }
381        else
382        {
383          throw new LDAPException(ResultCode.DECODING_ERROR,
384                                  ERR_ATTRTYPE_DECODE_MULTIPLE_ELEMENTS.get(
385                                       attributeTypeString,
386                                       "NO-USER-MODIFICATION"));
387        }
388      }
389      else if (lowerToken.equals("usage"))
390      {
391        if (attrUsage == null)
392        {
393          pos = skipSpaces(attributeTypeString, pos, length);
394
395          buffer = new StringBuilder();
396          pos = readOID(attributeTypeString, pos, length, buffer);
397
398          final String usageStr = StaticUtils.toLowerCase(buffer.toString());
399          if (usageStr.equals("userapplications"))
400          {
401            attrUsage = AttributeUsage.USER_APPLICATIONS;
402          }
403          else if (usageStr.equals("directoryoperation"))
404          {
405            attrUsage = AttributeUsage.DIRECTORY_OPERATION;
406          }
407          else if (usageStr.equals("distributedoperation"))
408          {
409            attrUsage = AttributeUsage.DISTRIBUTED_OPERATION;
410          }
411          else if (usageStr.equals("dsaoperation"))
412          {
413            attrUsage = AttributeUsage.DSA_OPERATION;
414          }
415          else
416          {
417            throw new LDAPException(ResultCode.DECODING_ERROR,
418                                    ERR_ATTRTYPE_DECODE_INVALID_USAGE.get(
419                                         attributeTypeString, usageStr));
420          }
421        }
422        else
423        {
424          throw new LDAPException(ResultCode.DECODING_ERROR,
425                                  ERR_ATTRTYPE_DECODE_MULTIPLE_ELEMENTS.get(
426                                       attributeTypeString, "USAGE"));
427        }
428      }
429      else if (lowerToken.startsWith("x-"))
430      {
431        pos = skipSpaces(attributeTypeString, pos, length);
432
433        final ArrayList<String> valueList = new ArrayList<>(5);
434        pos = readQDStrings(attributeTypeString, pos, length, token, valueList);
435
436        final String[] values = new String[valueList.size()];
437        valueList.toArray(values);
438
439        if (exts.containsKey(token))
440        {
441          throw new LDAPException(ResultCode.DECODING_ERROR,
442                                  ERR_ATTRTYPE_DECODE_DUP_EXT.get(
443                                       attributeTypeString, token));
444        }
445
446        exts.put(token, values);
447      }
448      else
449      {
450        throw new LDAPException(ResultCode.DECODING_ERROR,
451                                ERR_ATTRTYPE_DECODE_UNEXPECTED_TOKEN.get(
452                                     attributeTypeString, token));
453      }
454    }
455
456    description           = descr;
457    equalityMatchingRule  = eqRule;
458    orderingMatchingRule  = ordRule;
459    substringMatchingRule = subRule;
460    superiorType          = supType;
461    syntaxOID             = synOID;
462
463    names = new String[nameList.size()];
464    nameList.toArray(names);
465
466    isObsolete           = (obsolete != null);
467    isSingleValued       = (singleValue != null);
468    isCollective         = (collective != null);
469    isNoUserModification = (noUserMod != null);
470
471    if (attrUsage == null)
472    {
473      usage = AttributeUsage.USER_APPLICATIONS;
474    }
475    else
476    {
477      usage = attrUsage;
478    }
479
480    extensions = Collections.unmodifiableMap(exts);
481  }
482
483
484
485  /**
486   * Creates a new attribute type with the provided information.
487   *
488   * @param  oid                    The OID for this attribute type.  It must
489   *                                not be {@code null}.
490   * @param  name                   The name for this attribute type.  It may be
491   *                                {@code null} if the attribute type should
492   *                                only be referenced by OID.
493   * @param  description            The description for this attribute type.  It
494   *                                may be {@code null} if there is no
495   *                                description.
496   * @param  equalityMatchingRule   The name or OID of the equality matching
497   *                                rule for this attribute type.  It may be
498   *                                {@code null} if a default rule is to be
499   *                                inherited.
500   * @param  orderingMatchingRule   The name or OID of the ordering matching
501   *                                rule for this attribute type.  It may be
502   *                                {@code null} if a default rule is to be
503   *                                inherited.
504   * @param  substringMatchingRule  The name or OID of the substring matching
505   *                                rule for this attribute type.  It may be
506   *                                {@code null} if a default rule is to be
507   *                                inherited.
508   * @param  syntaxOID              The syntax OID for this attribute type.  It
509   *                                may be {@code null} if a default syntax is
510   *                                to be inherited.
511   * @param  isSingleValued         Indicates whether attributes of this type
512   *                                are only allowed to have a single value.
513   * @param  extensions             The set of extensions for this attribute
514   *                                type.  It may be {@code null} or empty if
515   *                                there should not be any extensions.
516   */
517  public AttributeTypeDefinition(@NotNull final String oid,
518               @Nullable final String name,
519               @Nullable final String description,
520               @Nullable final String equalityMatchingRule,
521               @Nullable final String orderingMatchingRule,
522               @Nullable final String substringMatchingRule,
523               @Nullable final String syntaxOID,
524               final boolean isSingleValued,
525               @Nullable final Map<String,String[]> extensions)
526  {
527    this(oid, ((name == null) ? null : new String[] { name }), description,
528         false, null, equalityMatchingRule, orderingMatchingRule,
529         substringMatchingRule, syntaxOID, isSingleValued, false, false,
530         AttributeUsage.USER_APPLICATIONS, extensions);
531  }
532
533
534
535  /**
536   * Creates a new attribute type with the provided information.
537   *
538   * @param  oid                    The OID for this attribute type.  It must
539   *                                not be {@code null}.
540   * @param  names                  The set of names for this attribute type.
541   *                                It may be {@code null} or empty if the
542   *                                attribute type should only be referenced by
543   *                                OID.
544   * @param  description            The description for this attribute type.  It
545   *                                may be {@code null} if there is no
546   *                                description.
547   * @param  isObsolete             Indicates whether this attribute type is
548   *                                declared obsolete.
549   * @param  superiorType           The name or OID of the superior attribute
550   *                                type.  It may be {@code null} if there is no
551   *                                superior type.
552   * @param  equalityMatchingRule   The name or OID of the equality matching
553   *                                rule for this attribute type.  It may be
554   *                                {@code null} if a default rule is to be
555   *                                inherited.
556   * @param  orderingMatchingRule   The name or OID of the ordering matching
557   *                                rule for this attribute type.  It may be
558   *                                {@code null} if a default rule is to be
559   *                                inherited.
560   * @param  substringMatchingRule  The name or OID of the substring matching
561   *                                rule for this attribute type.  It may be
562   *                                {@code null} if a default rule is to be
563   *                                inherited.
564   * @param  syntaxOID              The syntax OID for this attribute type.  It
565   *                                may be {@code null} if a default syntax is
566   *                                to be inherited.
567   * @param  isSingleValued         Indicates whether attributes of this type
568   *                                are only allowed to have a single value.
569   * @param  isCollective           Indicates whether this attribute type should
570   *                                be considered collective.
571   * @param  isNoUserModification   Indicates whether clients should be allowed
572   *                                to modify attributes of this type.
573   * @param  usage                  The attribute usage for this attribute type.
574   *                                It may be {@code null} if the default usage
575   *                                of userApplications is to be used.
576   * @param  extensions             The set of extensions for this attribute
577   *                                type.  It may be {@code null} or empty if
578   *                                there should not be any extensions.
579   */
580  public AttributeTypeDefinition(@NotNull final String oid,
581              @Nullable final String[] names,
582              @Nullable final String description,
583              final boolean isObsolete,
584              @Nullable final String superiorType,
585              @Nullable final String equalityMatchingRule,
586              @Nullable final String orderingMatchingRule,
587              @Nullable final String substringMatchingRule,
588              @Nullable final String syntaxOID,
589              final boolean isSingleValued,
590              final boolean isCollective,
591              final boolean isNoUserModification,
592              @Nullable final AttributeUsage usage,
593              @Nullable final Map<String,String[]> extensions)
594  {
595    Validator.ensureNotNull(oid);
596
597    this.oid                   = oid;
598    this.description           = description;
599    this.isObsolete            = isObsolete;
600    this.superiorType          = superiorType;
601    this.equalityMatchingRule  = equalityMatchingRule;
602    this.orderingMatchingRule  = orderingMatchingRule;
603    this.substringMatchingRule = substringMatchingRule;
604    this.syntaxOID             = syntaxOID;
605    this.isSingleValued        = isSingleValued;
606    this.isCollective          = isCollective;
607    this.isNoUserModification  = isNoUserModification;
608
609    if (names == null)
610    {
611      this.names = StaticUtils.NO_STRINGS;
612    }
613    else
614    {
615      this.names = names;
616    }
617
618    if (usage == null)
619    {
620      this.usage = AttributeUsage.USER_APPLICATIONS;
621    }
622    else
623    {
624      this.usage = usage;
625    }
626
627    if (extensions == null)
628    {
629      this.extensions = Collections.emptyMap();
630    }
631    else
632    {
633      this.extensions = Collections.unmodifiableMap(extensions);
634    }
635
636    final StringBuilder buffer = new StringBuilder();
637    createDefinitionString(buffer);
638    attributeTypeString = buffer.toString();
639  }
640
641
642
643  /**
644   * Constructs a string representation of this attribute type definition in the
645   * provided buffer.
646   *
647   * @param  buffer  The buffer in which to construct a string representation of
648   *                 this attribute type definition.
649   */
650  private void createDefinitionString(@NotNull final StringBuilder buffer)
651  {
652    buffer.append("( ");
653    buffer.append(oid);
654
655    if (names.length == 1)
656    {
657      buffer.append(" NAME '");
658      buffer.append(names[0]);
659      buffer.append('\'');
660    }
661    else if (names.length > 1)
662    {
663      buffer.append(" NAME (");
664      for (final String name : names)
665      {
666        buffer.append(" '");
667        buffer.append(name);
668        buffer.append('\'');
669      }
670      buffer.append(" )");
671    }
672
673    if (description != null)
674    {
675      buffer.append(" DESC '");
676      encodeValue(description, buffer);
677      buffer.append('\'');
678    }
679
680    if (isObsolete)
681    {
682      buffer.append(" OBSOLETE");
683    }
684
685    if (superiorType != null)
686    {
687      buffer.append(" SUP ");
688      buffer.append(superiorType);
689    }
690
691    if (equalityMatchingRule != null)
692    {
693      buffer.append(" EQUALITY ");
694      buffer.append(equalityMatchingRule);
695    }
696
697    if (orderingMatchingRule != null)
698    {
699      buffer.append(" ORDERING ");
700      buffer.append(orderingMatchingRule);
701    }
702
703    if (substringMatchingRule != null)
704    {
705      buffer.append(" SUBSTR ");
706      buffer.append(substringMatchingRule);
707    }
708
709    if (syntaxOID != null)
710    {
711      buffer.append(" SYNTAX ");
712      buffer.append(syntaxOID);
713    }
714
715    if (isSingleValued)
716    {
717      buffer.append(" SINGLE-VALUE");
718    }
719
720    if (isCollective)
721    {
722      buffer.append(" COLLECTIVE");
723    }
724
725    if (isNoUserModification)
726    {
727      buffer.append(" NO-USER-MODIFICATION");
728    }
729
730    buffer.append(" USAGE ");
731    buffer.append(usage.getName());
732
733    for (final Map.Entry<String,String[]> e : extensions.entrySet())
734    {
735      final String   name   = e.getKey();
736      final String[] values = e.getValue();
737      if (values.length == 1)
738      {
739        buffer.append(' ');
740        buffer.append(name);
741        buffer.append(" '");
742        encodeValue(values[0], buffer);
743        buffer.append('\'');
744      }
745      else
746      {
747        buffer.append(' ');
748        buffer.append(name);
749        buffer.append(" (");
750        for (final String value : values)
751        {
752          buffer.append(" '");
753          encodeValue(value, buffer);
754          buffer.append('\'');
755        }
756        buffer.append(" )");
757      }
758    }
759
760    buffer.append(" )");
761  }
762
763
764
765  /**
766   * Retrieves the OID for this attribute type.
767   *
768   * @return  The OID for this attribute type.
769   */
770  @NotNull()
771  public String getOID()
772  {
773    return oid;
774  }
775
776
777
778  /**
779   * Retrieves the set of names for this attribute type.
780   *
781   * @return  The set of names for this attribute type, or an empty array if it
782   *          does not have any names.
783   */
784  @NotNull()
785  public String[] getNames()
786  {
787    return names;
788  }
789
790
791
792  /**
793   * Retrieves the primary name that can be used to reference this attribute
794   * type.  If one or more names are defined, then the first name will be used.
795   * Otherwise, the OID will be returned.
796   *
797   * @return  The primary name that can be used to reference this attribute
798   *          type.
799   */
800  @NotNull()
801  public String getNameOrOID()
802  {
803    if (names.length == 0)
804    {
805      return oid;
806    }
807    else
808    {
809      return names[0];
810    }
811  }
812
813
814
815  /**
816   * Indicates whether the provided string matches the OID or any of the names
817   * for this attribute type.
818   *
819   * @param  s  The string for which to make the determination.  It must not be
820   *            {@code null}.
821   *
822   * @return  {@code true} if the provided string matches the OID or any of the
823   *          names for this attribute type, or {@code false} if not.
824   */
825  public boolean hasNameOrOID(@NotNull final String s)
826  {
827    for (final String name : names)
828    {
829      if (s.equalsIgnoreCase(name))
830      {
831        return true;
832      }
833    }
834
835    return s.equalsIgnoreCase(oid);
836  }
837
838
839
840  /**
841   * Retrieves the description for this attribute type, if available.
842   *
843   * @return  The description for this attribute type, or {@code null} if there
844   *          is no description defined.
845   */
846  @Nullable()
847  public String getDescription()
848  {
849    return description;
850  }
851
852
853
854  /**
855   * Indicates whether this attribute type is declared obsolete.
856   *
857   * @return  {@code true} if this attribute type is declared obsolete, or
858   *          {@code false} if it is not.
859   */
860  public boolean isObsolete()
861  {
862    return isObsolete;
863  }
864
865
866
867  /**
868   * Retrieves the name or OID of the superior type for this attribute type, if
869   * available.
870   *
871   * @return  The name or OID of the superior type for this attribute type, or
872   *          {@code null} if no superior type is defined.
873   */
874  @Nullable()
875  public String getSuperiorType()
876  {
877    return superiorType;
878  }
879
880
881
882  /**
883   * Retrieves the superior attribute type definition for this attribute type,
884   * if available.
885   *
886   * @param  schema  The schema to use to get the superior attribute type.
887   *
888   * @return  The superior attribute type definition for this attribute type, or
889   *          {@code null} if no superior type is defined, or if the superior
890   *          type is not included in the provided schema.
891   */
892  @Nullable()
893  public AttributeTypeDefinition getSuperiorType(@NotNull final Schema schema)
894  {
895    if (superiorType != null)
896    {
897      return schema.getAttributeType(superiorType);
898    }
899
900    return null;
901  }
902
903
904
905  /**
906   * Retrieves the name or OID of the equality matching rule for this attribute
907   * type, if available.
908   *
909   * @return  The name or OID of the equality matching rule for this attribute
910   *          type, or {@code null} if no equality matching rule is defined or a
911   *          default rule will be inherited.
912   */
913  @Nullable()
914  public String getEqualityMatchingRule()
915  {
916    return equalityMatchingRule;
917  }
918
919
920
921  /**
922   * Retrieves the name or OID of the equality matching rule for this attribute
923   * type, examining superior attribute types if necessary.
924   *
925   * @param  schema  The schema to use to get the superior attribute type.
926   *
927   * @return  The name or OID of the equality matching rule for this attribute
928   *          type, or {@code null} if no equality matching rule is defined.
929   */
930  @Nullable()
931  public String getEqualityMatchingRule(@NotNull final Schema schema)
932  {
933    if (equalityMatchingRule == null)
934    {
935      final AttributeTypeDefinition sup = getSuperiorType(schema);
936      if (sup != null)
937      {
938        return sup.getEqualityMatchingRule(schema);
939      }
940    }
941
942    return equalityMatchingRule;
943  }
944
945
946
947  /**
948   * Retrieves the name or OID of the ordering matching rule for this attribute
949   * type, if available.
950   *
951   * @return  The name or OID of the ordering matching rule for this attribute
952   *          type, or {@code null} if no ordering matching rule is defined or a
953   *          default rule will be inherited.
954   */
955  @Nullable()
956  public String getOrderingMatchingRule()
957  {
958    return orderingMatchingRule;
959  }
960
961
962
963  /**
964   * Retrieves the name or OID of the ordering matching rule for this attribute
965   * type, examining superior attribute types if necessary.
966   *
967   * @param  schema  The schema to use to get the superior attribute type.
968   *
969   * @return  The name or OID of the ordering matching rule for this attribute
970   *          type, or {@code null} if no ordering matching rule is defined.
971   */
972  @Nullable()
973  public String getOrderingMatchingRule(@NotNull final Schema schema)
974  {
975    if (orderingMatchingRule == null)
976    {
977      final AttributeTypeDefinition sup = getSuperiorType(schema);
978      if (sup != null)
979      {
980        return sup.getOrderingMatchingRule(schema);
981      }
982    }
983
984    return orderingMatchingRule;
985  }
986
987
988
989  /**
990   * Retrieves the name or OID of the substring matching rule for this attribute
991   * type, if available.
992   *
993   * @return  The name or OID of the substring matching rule for this attribute
994   *          type, or {@code null} if no substring matching rule is defined or
995   *          a default rule will be inherited.
996   */
997  @Nullable()
998  public String getSubstringMatchingRule()
999  {
1000    return substringMatchingRule;
1001  }
1002
1003
1004
1005  /**
1006   * Retrieves the name or OID of the substring matching rule for this attribute
1007   * type, examining superior attribute types if necessary.
1008   *
1009   * @param  schema  The schema to use to get the superior attribute type.
1010   *
1011   * @return  The name or OID of the substring matching rule for this attribute
1012   *          type, or {@code null} if no substring matching rule is defined.
1013   */
1014  @Nullable()
1015  public String getSubstringMatchingRule(@NotNull final Schema schema)
1016  {
1017    if (substringMatchingRule == null)
1018    {
1019      final AttributeTypeDefinition sup = getSuperiorType(schema);
1020      if (sup != null)
1021      {
1022        return sup.getSubstringMatchingRule(schema);
1023      }
1024    }
1025
1026    return substringMatchingRule;
1027  }
1028
1029
1030
1031  /**
1032   * Retrieves the OID of the syntax for this attribute type, if available.  It
1033   * may optionally include a minimum upper bound in curly braces.
1034   *
1035   * @return  The OID of the syntax for this attribute type, or {@code null} if
1036   *          the syntax will be inherited.
1037   */
1038  @Nullable()
1039  public String getSyntaxOID()
1040  {
1041    return syntaxOID;
1042  }
1043
1044
1045
1046  /**
1047   * Retrieves the OID of the syntax for this attribute type, examining superior
1048   * types if necessary.  It may optionally include a minimum upper bound in
1049   * curly braces.
1050   *
1051   * @param  schema  The schema to use to get the superior attribute type.
1052   *
1053   * @return  The OID of the syntax for this attribute type, or {@code null} if
1054   *          no syntax is defined.
1055   */
1056  @Nullable()
1057  public String getSyntaxOID(@NotNull final Schema schema)
1058  {
1059    if (syntaxOID == null)
1060    {
1061      final AttributeTypeDefinition sup = getSuperiorType(schema);
1062      if (sup != null)
1063      {
1064        return sup.getSyntaxOID(schema);
1065      }
1066    }
1067
1068    return syntaxOID;
1069  }
1070
1071
1072
1073  /**
1074   * Retrieves the OID of the syntax for this attribute type, if available.  If
1075   * the attribute type definition includes a minimum upper bound in curly
1076   * braces, it will be removed from the value that is returned.
1077   *
1078   * @return  The OID of the syntax for this attribute type, or {@code null} if
1079   *          the syntax will be inherited.
1080   */
1081  @Nullable()
1082  public String getBaseSyntaxOID()
1083  {
1084    return getBaseSyntaxOID(syntaxOID);
1085  }
1086
1087
1088
1089  /**
1090   * Retrieves the base OID of the syntax for this attribute type, examining
1091   * superior types if necessary.    If the attribute type definition includes a
1092   * minimum upper bound in curly braces, it will be removed from the value that
1093   * is returned.
1094   *
1095   * @param  schema  The schema to use to get the superior attribute type, if
1096   *                 necessary.
1097   *
1098   * @return  The OID of the syntax for this attribute type, or {@code null} if
1099   *          no syntax is defined.
1100   */
1101  @Nullable()
1102  public String getBaseSyntaxOID(@NotNull final Schema schema)
1103  {
1104    return getBaseSyntaxOID(getSyntaxOID(schema));
1105  }
1106
1107
1108
1109  /**
1110   * Retrieves the base OID of the syntax for this attribute type, examining
1111   * superior types if necessary.    If the attribute type definition includes a
1112   * minimum upper bound in curly braces, it will be removed from the value that
1113   * is returned.
1114   *
1115   * @param  syntaxOID  The syntax OID (optionally including the minimum upper
1116   *                    bound element) to examine.
1117   *
1118   * @return  The OID of the syntax for this attribute type, or {@code null} if
1119   *          no syntax is defined.
1120   */
1121  @Nullable()
1122  public static String getBaseSyntaxOID(@Nullable final String syntaxOID)
1123  {
1124    if (syntaxOID == null)
1125    {
1126      return null;
1127    }
1128
1129    final int curlyPos = syntaxOID.indexOf('{');
1130    if (curlyPos > 0)
1131    {
1132      return syntaxOID.substring(0, curlyPos);
1133    }
1134    else
1135    {
1136      return syntaxOID;
1137    }
1138  }
1139
1140
1141
1142  /**
1143   * Retrieves the value of the minimum upper bound element of the syntax
1144   * definition for this attribute type, if defined.  If a minimum upper bound
1145   * is present (as signified by an integer value in curly braces immediately
1146   * following the syntax OID without any space between them), then it should
1147   * serve as an indication to the directory server that it should be prepared
1148   * to handle values with at least that number of (possibly multi-byte)
1149   * characters.
1150   *
1151   * @return  The value of the minimum upper bound element of the syntax
1152   *          definition for this attribute type, or -1 if no syntax is defined
1153   *          defined or if it does not have a valid minimum upper bound.
1154   */
1155  public int getSyntaxMinimumUpperBound()
1156  {
1157    return getSyntaxMinimumUpperBound(syntaxOID);
1158  }
1159
1160
1161
1162  /**
1163   * Retrieves the value of the minimum upper bound element of the syntax
1164   * definition for this attribute type, if defined.  If a minimum upper bound
1165   * is present (as signified by an integer value in curly braces immediately
1166   * following the syntax OID without any space between them), then it should
1167   * serve as an indication to the directory server that it should be prepared
1168   * to handle values with at least that number of (possibly multi-byte)
1169   * characters.
1170   *
1171   * @param  schema  The schema to use to get the superior attribute type, if
1172   *                 necessary.
1173   *
1174   * @return  The value of the minimum upper bound element of the syntax
1175   *          definition for this attribute type, or -1 if no syntax is defined
1176   *          defined or if it does not have a valid minimum upper bound.
1177   */
1178  public int getSyntaxMinimumUpperBound(@NotNull final Schema schema)
1179  {
1180    return getSyntaxMinimumUpperBound(getSyntaxOID(schema));
1181  }
1182
1183
1184
1185  /**
1186   * Retrieves the value of the minimum upper bound element of the syntax
1187   * definition for this attribute type, if defined.  If a minimum upper bound
1188   * is present (as signified by an integer value in curly braces immediately
1189   * following the syntax OID without any space between them), then it should
1190   * serve as an indication to the directory server that it should be prepared
1191   * to handle values with at least that number of (possibly multi-byte)
1192   * characters.
1193   *
1194   * @param  syntaxOID  The syntax OID (optionally including the minimum upper
1195   *                    bound element) to examine.
1196   *
1197   * @return  The value of the minimum upper bound element of the provided
1198   *          syntax OID, or -1 if the provided syntax OID is {@code null} or
1199   *          does not have a valid minimum upper bound.
1200   */
1201  public static int getSyntaxMinimumUpperBound(@Nullable final String syntaxOID)
1202  {
1203    if (syntaxOID == null)
1204    {
1205      return -1;
1206    }
1207
1208    final int curlyPos = syntaxOID.indexOf('{');
1209    if ((curlyPos > 0) && syntaxOID.endsWith("}"))
1210    {
1211      try
1212      {
1213        return Integer.parseInt(syntaxOID.substring(curlyPos+1,
1214             syntaxOID.length()-1));
1215      }
1216      catch (final Exception e)
1217      {
1218        Debug.debugException(e);
1219        return -1;
1220      }
1221    }
1222    else
1223    {
1224      return -1;
1225    }
1226  }
1227
1228
1229
1230  /**
1231   * Indicates whether this attribute type is declared single-valued, and
1232   * therefore attributes of this type will only be allowed to have at most one
1233   * value.
1234   *
1235   * @return  {@code true} if this attribute type is declared single-valued, or
1236   *          {@code false} if not.
1237   */
1238  public boolean isSingleValued()
1239  {
1240    return isSingleValued;
1241  }
1242
1243
1244
1245  /**
1246   * Indicates whether this attribute type is declared collective, and therefore
1247   * values may be dynamically generated as described in RFC 3671.
1248   *
1249   * @return  {@code true} if this attribute type is declared collective, or
1250   *          {@code false} if not.
1251   */
1252  public boolean isCollective()
1253  {
1254    return isCollective;
1255  }
1256
1257
1258
1259  /**
1260   * Indicates whether this attribute type is declared no-user-modification,
1261   * and therefore attributes of this type will not be allowed to be altered
1262   * by clients.
1263   *
1264   * @return  {@code true} if this attribute type is declared
1265   *          no-user-modification, or {@code false} if not.
1266   */
1267  public boolean isNoUserModification()
1268  {
1269    return isNoUserModification;
1270  }
1271
1272
1273
1274  /**
1275   * Retrieves the attribute usage for this attribute type.
1276   *
1277   * @return  The attribute usage for this attribute type.
1278   */
1279  @NotNull()
1280  public AttributeUsage getUsage()
1281  {
1282    return usage;
1283  }
1284
1285
1286
1287  /**
1288   * Indicates whether this attribute type has an operational attribute usage.
1289   *
1290   * @return  {@code true} if this attribute type has an operational attribute
1291   *          usage, or {@code false} if not.
1292   */
1293  public boolean isOperational()
1294  {
1295    return usage.isOperational();
1296  }
1297
1298
1299
1300  /**
1301   * Retrieves the set of extensions for this attribute type.  They will be
1302   * mapped from the extension name (which should start with "X-") to the set of
1303   * values for that extension.
1304   *
1305   * @return  The set of extensions for this attribute type.
1306   */
1307  @NotNull()
1308  public Map<String,String[]> getExtensions()
1309  {
1310    return extensions;
1311  }
1312
1313
1314
1315  /**
1316   * {@inheritDoc}
1317   */
1318  @Override()
1319  @NotNull()
1320  public SchemaElementType getSchemaElementType()
1321  {
1322    return SchemaElementType.ATTRIBUTE_TYPE;
1323  }
1324
1325
1326
1327  /**
1328   * {@inheritDoc}
1329   */
1330  @Override()
1331  public int hashCode()
1332  {
1333    return oid.hashCode();
1334  }
1335
1336
1337
1338  /**
1339   * {@inheritDoc}
1340   */
1341  @Override()
1342  public boolean equals(@Nullable final Object o)
1343  {
1344    if (o == null)
1345    {
1346      return false;
1347    }
1348
1349    if (o == this)
1350    {
1351      return true;
1352    }
1353
1354    if (! (o instanceof AttributeTypeDefinition))
1355    {
1356      return false;
1357    }
1358
1359    final AttributeTypeDefinition d = (AttributeTypeDefinition) o;
1360    return(oid.equals(d.oid) &&
1361         StaticUtils.stringsEqualIgnoreCaseOrderIndependent(names, d.names) &&
1362         StaticUtils.bothNullOrEqual(usage, d.usage) &&
1363         StaticUtils.bothNullOrEqualIgnoreCase(description, d.description) &&
1364         StaticUtils.bothNullOrEqualIgnoreCase(equalityMatchingRule,
1365              d.equalityMatchingRule) &&
1366         StaticUtils.bothNullOrEqualIgnoreCase(orderingMatchingRule,
1367              d.orderingMatchingRule) &&
1368         StaticUtils.bothNullOrEqualIgnoreCase(substringMatchingRule,
1369              d.substringMatchingRule) &&
1370         StaticUtils.bothNullOrEqualIgnoreCase(superiorType, d.superiorType) &&
1371         StaticUtils.bothNullOrEqualIgnoreCase(syntaxOID, d.syntaxOID) &&
1372         (isCollective == d.isCollective) &&
1373         (isNoUserModification == d.isNoUserModification) &&
1374         (isObsolete == d.isObsolete) &&
1375         (isSingleValued == d.isSingleValued) &&
1376         extensionsEqual(extensions, d.extensions));
1377  }
1378
1379
1380
1381  /**
1382   * Retrieves a string representation of this attribute type definition, in the
1383   * format described in RFC 4512 section 4.1.2.
1384   *
1385   * @return  A string representation of this attribute type definition.
1386   */
1387  @Override()
1388  @NotNull()
1389  public String toString()
1390  {
1391    return attributeTypeString;
1392  }
1393}