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