001    /*
002     * Copyright 2007-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 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          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    }