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