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