001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.schema;
028    import org.opends.messages.Message;
029    
030    
031    
032    import java.util.ArrayList;
033    import java.util.HashMap;
034    import java.util.LinkedHashMap;
035    import java.util.LinkedList;
036    import java.util.List;
037    
038    import org.opends.server.admin.std.server.*;
039    import org.opends.server.admin.server.ConfigurationChangeListener;
040    import org.opends.server.api.ApproximateMatchingRule;
041    import org.opends.server.api.AttributeSyntax;
042    import org.opends.server.api.EqualityMatchingRule;
043    import org.opends.server.api.OrderingMatchingRule;
044    import org.opends.server.api.SubstringMatchingRule;
045    import org.opends.server.config.ConfigException;
046    import org.opends.server.core.DirectoryServer;
047    
048    import static org.opends.server.loggers.debug.DebugLogger.*;
049    import org.opends.server.loggers.debug.DebugTracer;
050    import org.opends.server.types.*;
051    import static org.opends.messages.SchemaMessages.*;
052    
053    import org.opends.messages.MessageBuilder;
054    import static org.opends.server.schema.SchemaConstants.*;
055    import static org.opends.server.util.ServerConstants.*;
056    import static org.opends.server.util.StaticUtils.*;
057    
058    
059    
060    /**
061     * This class defines the attribute type description syntax, which is used to
062     * hold attribute type definitions in the server schema.  The format of this
063     * syntax is defined in RFC 2252.
064     */
065    public class AttributeTypeSyntax
066           extends AttributeSyntax<AttributeTypeDescriptionAttributeSyntaxCfg>
067           implements
068           ConfigurationChangeListener<AttributeTypeDescriptionAttributeSyntaxCfg> {
069    
070      /**
071       * The tracer object for the debug logger.
072       */
073      private static final DebugTracer TRACER = getTracer();
074    
075    
076    
077      // The reference to the configuration for this attribute type description
078      // syntax.
079      private AttributeTypeDescriptionAttributeSyntaxCfg currentConfig;
080    
081    
082    
083      // The default equality matching rule for this syntax.
084      private EqualityMatchingRule defaultEqualityMatchingRule;
085    
086      // The default ordering matching rule for this syntax.
087      private OrderingMatchingRule defaultOrderingMatchingRule;
088    
089      // The default substring matching rule for this syntax.
090      private SubstringMatchingRule defaultSubstringMatchingRule;
091    
092      // If true strip the suggested minimum upper bound from the syntax OID.
093      private static boolean stripMinimumUpperBound=false;
094    
095    
096      /**
097       * Creates a new instance of this syntax.  Note that the only thing that
098       * should be done here is to invoke the default constructor for the
099       * superclass.  All initialization should be performed in the
100       * <CODE>initializeSyntax</CODE> method.
101       */
102      public AttributeTypeSyntax()
103      {
104        super();
105      }
106    
107    
108    
109      /**
110       * {@inheritDoc}
111       */
112      public void
113      initializeSyntax(AttributeTypeDescriptionAttributeSyntaxCfg configuration)
114             throws ConfigException, InitializationException
115      {
116        defaultEqualityMatchingRule =
117             DirectoryServer.getEqualityMatchingRule(EMR_CASE_IGNORE_OID);
118        if (defaultEqualityMatchingRule == null)
119        {
120          Message message = ERR_ATTR_SYNTAX_UNKNOWN_EQUALITY_MATCHING_RULE.get(
121              EMR_CASE_IGNORE_OID, SYNTAX_ATTRIBUTE_TYPE_NAME);
122          throw new InitializationException(message);
123        }
124    
125        defaultOrderingMatchingRule =
126             DirectoryServer.getOrderingMatchingRule(OMR_CASE_IGNORE_OID);
127        if (defaultOrderingMatchingRule == null)
128        {
129          Message message = ERR_ATTR_SYNTAX_UNKNOWN_ORDERING_MATCHING_RULE.get(
130              OMR_CASE_IGNORE_OID, SYNTAX_ATTRIBUTE_TYPE_NAME);
131          throw new InitializationException(message);
132        }
133    
134        defaultSubstringMatchingRule =
135             DirectoryServer.getSubstringMatchingRule(SMR_CASE_IGNORE_OID);
136        if (defaultSubstringMatchingRule == null)
137        {
138          Message message = ERR_ATTR_SYNTAX_UNKNOWN_SUBSTRING_MATCHING_RULE.get(
139              SMR_CASE_IGNORE_OID, SYNTAX_ATTRIBUTE_TYPE_NAME);
140          throw new InitializationException(message);
141        }
142    
143        // This syntax is one of the Directory Server's core syntaxes and therefore
144        // it may be instantiated at times without a configuration entry.  If that
145        // is the case, then we'll exit now before doing anything that could require
146        // access to that entry.
147        if (configuration == null)
148        {
149          return;
150        }
151    
152        currentConfig = configuration;
153        currentConfig.addAttributeTypeDescriptionChangeListener(this);
154        stripMinimumUpperBound=configuration.isStripSyntaxMinUpperBound();
155      }
156    
157    
158    
159      /**
160       * {@inheritDoc}
161       */
162      public String getSyntaxName()
163      {
164        return SYNTAX_ATTRIBUTE_TYPE_NAME;
165      }
166    
167    
168    
169      /**
170       * {@inheritDoc}
171       */
172      public String getOID()
173      {
174        return SYNTAX_ATTRIBUTE_TYPE_OID;
175      }
176    
177    
178    
179      /**
180       * {@inheritDoc}
181       */
182      public String getDescription()
183      {
184        return SYNTAX_ATTRIBUTE_TYPE_DESCRIPTION;
185      }
186    
187    
188    
189      /**
190       * {@inheritDoc}
191       */
192      public EqualityMatchingRule getEqualityMatchingRule()
193      {
194        return defaultEqualityMatchingRule;
195      }
196    
197    
198    
199      /**
200       * {@inheritDoc}
201       */
202      public OrderingMatchingRule getOrderingMatchingRule()
203      {
204        return defaultOrderingMatchingRule;
205      }
206    
207    
208    
209      /**
210       * {@inheritDoc}
211       */
212      public SubstringMatchingRule getSubstringMatchingRule()
213      {
214        return defaultSubstringMatchingRule;
215      }
216    
217    
218    
219      /**
220       * {@inheritDoc}
221       */
222      public ApproximateMatchingRule getApproximateMatchingRule()
223      {
224        // There is no approximate matching rule by default.
225        return null;
226      }
227    
228    
229    
230      /**
231       * {@inheritDoc}
232       */
233      public boolean valueIsAcceptable(ByteString value,
234                                       MessageBuilder invalidReason)
235      {
236        // We'll use the decodeAttributeType method to determine if the value is
237        // acceptable.
238        try
239        {
240          decodeAttributeType(value, DirectoryServer.getSchema(), true);
241          return true;
242        }
243        catch (DirectoryException de)
244        {
245          if (debugEnabled())
246          {
247            TRACER.debugCaught(DebugLogLevel.ERROR, de);
248          }
249    
250          invalidReason.append(de.getMessageObject());
251          return false;
252        }
253      }
254    
255    
256    
257      /**
258       * Decodes the contents of the provided ASN.1 octet string as an attribute
259       * type definition according to the rules of this syntax.  Note that the
260       * provided octet string value does not need to be normalized (and in fact, it
261       * should not be in order to allow the desired capitalization to be
262       * preserved).
263       *
264       * @param  value                 The ASN.1 octet string containing the value
265       *                               to decode (it does not need to be
266       *                               normalized).
267       * @param  schema                The schema to use to resolve references to
268       *                               other schema elements.
269       * @param  allowUnknownElements  Indicates whether to allow values that
270       *                               reference a superior attribute type which are
271       *                               not defined in the server schema. This should
272       *                               only be true when called by
273       *                               {@code valueIsAcceptable}.
274       *
275       * @return  The decoded attribute type definition.
276       *
277       * @throws  DirectoryException  If the provided value cannot be decoded as an
278       *                              attribute type definition.
279       */
280      public static AttributeType decodeAttributeType(ByteString value,
281                                                      Schema schema,
282                                                      boolean allowUnknownElements)
283             throws DirectoryException
284      {
285        // Get string representations of the provided value using the provided form
286        // and with all lowercase characters.
287        String valueStr = value.stringValue();
288        String lowerStr = toLowerCase(valueStr);
289    
290    
291        // We'll do this a character at a time.  First, skip over any leading
292        // whitespace.
293        int pos    = 0;
294        int length = valueStr.length();
295        while ((pos < length) && (valueStr.charAt(pos) == ' '))
296        {
297          pos++;
298        }
299    
300        if (pos >= length)
301        {
302          // This means that the value was empty or contained only whitespace.  That
303          // is illegal.
304          Message message = ERR_ATTR_SYNTAX_ATTRTYPE_EMPTY_VALUE.get();
305          throw new DirectoryException(
306                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
307        }
308    
309    
310        // The next character must be an open parenthesis.  If it is not, then that
311        // is an error.
312        char c = valueStr.charAt(pos++);
313        if (c != '(')
314        {
315          Message message = ERR_ATTR_SYNTAX_ATTRTYPE_EXPECTED_OPEN_PARENTHESIS.get(
316              valueStr, (pos-1), String.valueOf(c));
317          throw new DirectoryException(
318                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
319        }
320    
321    
322        // Skip over any spaces immediately following the opening parenthesis.
323        while ((pos < length) && ((c = valueStr.charAt(pos)) == ' '))
324        {
325          pos++;
326        }
327    
328        if (pos >= length)
329        {
330          // This means that the end of the value was reached before we could find
331          // the OID.  Ths is illegal.
332          Message message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
333          throw new DirectoryException(
334                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
335        }
336    
337    
338        // The next set of characters must be the OID.  Strictly speaking, this
339        // should only be a numeric OID, but we'll also allow for the
340        // "attrname-oid" case as well.  Look at the first character to figure out
341        // which we will be using.
342        int oidStartPos = pos;
343        if (isDigit(c))
344        {
345          // This must be a numeric OID.  In that case, we will accept only digits
346          // and periods, but not consecutive periods.
347          boolean lastWasPeriod = false;
348          while ((pos < length) && ((c = valueStr.charAt(pos++)) != ' '))
349          {
350            if (c == '.')
351            {
352              if (lastWasPeriod)
353              {
354                Message message =
355                  ERR_ATTR_SYNTAX_ATTRTYPE_DOUBLE_PERIOD_IN_NUMERIC_OID.
356                      get(valueStr, (pos-1));
357                throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
358                                             message);
359              }
360              else
361              {
362                lastWasPeriod = true;
363              }
364            }
365            else if (! isDigit(c))
366            {
367              // This must have been an illegal character.
368              Message message =
369                ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_NUMERIC_OID.
370                    get(valueStr, String.valueOf(c), (pos-1));
371              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
372                                           message);
373            }
374            else
375            {
376              lastWasPeriod = false;
377            }
378          }
379        }
380        else
381        {
382          // This must be a "fake" OID.  In this case, we will only accept
383          // alphabetic characters, numeric digits, and the hyphen.
384          while ((pos < length) && ((c = valueStr.charAt(pos++)) != ' '))
385          {
386            if (isAlpha(c) || isDigit(c) || (c == '-') ||
387                ((c == '_') && DirectoryServer.allowAttributeNameExceptions()))
388            {
389              // This is fine.  It is an acceptable character.
390            }
391            else
392            {
393              // This must have been an illegal character.
394              Message message = ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_STRING_OID.
395                  get(valueStr, String.valueOf(c), (pos-1));
396              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
397                                           message);
398            }
399          }
400        }
401    
402    
403        // If we're at the end of the value, then it isn't a valid attribute type
404        // description.  Otherwise, parse out the OID.
405        String oid;
406        if (pos >= length)
407        {
408          Message message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
409          throw new DirectoryException(
410                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
411        }
412        else
413        {
414          oid = lowerStr.substring(oidStartPos, (pos-1));
415        }
416    
417    
418        // Skip over the space(s) after the OID.
419        while ((pos < length) && ((c = valueStr.charAt(pos)) == ' '))
420        {
421          pos++;
422        }
423    
424        if (pos >= length)
425        {
426          // This means that the end of the value was reached before we could find
427          // the OID.  Ths is illegal.
428          Message message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
429          throw new DirectoryException(
430                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
431        }
432    
433    
434        // At this point, we should have a pretty specific syntax that describes
435        // what may come next, but some of the components are optional and it would
436        // be pretty easy to put something in the wrong order, so we will be very
437        // flexible about what we can accept.  Just look at the next token, figure
438        // out what it is and how to treat what comes after it, then repeat until
439        // we get to the end of the value.  But before we start, set default values
440        // for everything else we might need to know.
441        String  primaryName = oid;
442        List<String> typeNames = new LinkedList<String>();
443        String description = null;
444        AttributeType superiorType = null;
445        AttributeSyntax syntax = DirectoryServer.getDefaultAttributeSyntax();
446        ApproximateMatchingRule approximateMatchingRule = null;
447        EqualityMatchingRule equalityMatchingRule = null;
448        OrderingMatchingRule orderingMatchingRule = null;
449        SubstringMatchingRule substringMatchingRule = null;
450        AttributeUsage attributeUsage = AttributeUsage.USER_APPLICATIONS;
451        boolean isCollective = false;
452        boolean isNoUserModification = false;
453        boolean isObsolete = false;
454        boolean isSingleValue = false;
455        HashMap<String,List<String>> extraProperties =
456             new LinkedHashMap<String,List<String>>();
457    
458    
459        while (true)
460        {
461          StringBuilder tokenNameBuffer = new StringBuilder();
462          pos = readTokenName(valueStr, tokenNameBuffer, pos);
463          String tokenName = tokenNameBuffer.toString();
464          String lowerTokenName = toLowerCase(tokenName);
465          if (tokenName.equals(")"))
466          {
467            // We must be at the end of the value.  If not, then that's a problem.
468            if (pos < length)
469            {
470              Message message =
471                ERR_ATTR_SYNTAX_ATTRTYPE_UNEXPECTED_CLOSE_PARENTHESIS.
472                    get(valueStr, (pos-1));
473              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
474                                           message);
475            }
476    
477            break;
478          }
479          else if (lowerTokenName.equals("name"))
480          {
481            // This specifies the set of names for the attribute type.  It may be a
482            // single name in single quotes, or it may be an open parenthesis
483            // followed by one or more names in single quotes separated by spaces.
484            c = valueStr.charAt(pos++);
485            if (c == '\'')
486            {
487              StringBuilder userBuffer  = new StringBuilder();
488              StringBuilder lowerBuffer = new StringBuilder();
489              pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer,
490                                     (pos-1));
491              primaryName = userBuffer.toString();
492              typeNames.add(primaryName);
493            }
494            else if (c == '(')
495            {
496              StringBuilder userBuffer  = new StringBuilder();
497              StringBuilder lowerBuffer = new StringBuilder();
498              pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer,
499                                     pos);
500              primaryName = userBuffer.toString();
501              typeNames.add(primaryName);
502    
503    
504              while (true)
505              {
506                if (valueStr.charAt(pos) == ')')
507                {
508                  // Skip over any spaces after the parenthesis.
509                  pos++;
510                  while ((pos < length) && ((c = valueStr.charAt(pos)) == ' '))
511                  {
512                    pos++;
513                  }
514    
515                  break;
516                }
517                else
518                {
519                  userBuffer  = new StringBuilder();
520                  lowerBuffer = new StringBuilder();
521    
522                  pos = readQuotedString(valueStr, lowerStr, userBuffer,
523                                         lowerBuffer, pos);
524                  typeNames.add(userBuffer.toString());
525                }
526              }
527            }
528            else
529            {
530              // This is an illegal character.
531              Message message =
532                  ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR.get(
533                          valueStr, String.valueOf(c), (pos-1));
534              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
535                                           message);
536            }
537          }
538          else if (lowerTokenName.equals("desc"))
539          {
540            // This specifies the description for the attribute type.  It is an
541            // arbitrary string of characters enclosed in single quotes.
542            StringBuilder descriptionBuffer = new StringBuilder();
543            pos = readQuotedString(valueStr, descriptionBuffer, pos);
544            description = descriptionBuffer.toString();
545          }
546          else if (lowerTokenName.equals("obsolete"))
547          {
548            // This indicates whether the attribute type should be considered
549            // obsolete.  We do not need to do any more parsing for this token.
550            isObsolete = true;
551          }
552          else if (lowerTokenName.equals("sup"))
553          {
554            // This specifies the name or OID of the superior attribute type from
555            // which this attribute type should inherit its properties.
556            StringBuilder woidBuffer = new StringBuilder();
557            pos = readWOID(lowerStr, woidBuffer, pos);
558            superiorType = schema.getAttributeType(woidBuffer.toString());
559            if (superiorType == null)
560            {
561              if (allowUnknownElements)
562              {
563                superiorType = DirectoryServer.getDefaultAttributeType(
564                                                    woidBuffer.toString());
565              }
566              else
567              {
568                // This is bad because we don't know what the superior attribute
569                // type is so we can't base this attribute type on it.
570                Message message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_SUPERIOR_TYPE.
571                    get(String.valueOf(oid), String.valueOf(woidBuffer));
572                throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
573                                             message);
574              }
575            }
576    
577    
578            // Use the information in the superior type to provide defaults for the
579            // rest of the components in this attribute type description.
580            // Technically, the definition of the superior type should be provided
581            // before the matching rule, syntax, single-value, collective,
582            // no-user-modification, and usage components, and in that case we won't
583            // undo something else that has already been set by an earlier
584            // definition.  However, if the information is provided out-of-order,
585            // then it is possible that this could overwrite some desired setting
586            // that is different from that of the supertype.
587            approximateMatchingRule = superiorType.getApproximateMatchingRule();
588            equalityMatchingRule    = superiorType.getEqualityMatchingRule();
589            orderingMatchingRule    = superiorType.getOrderingMatchingRule();
590            substringMatchingRule   = superiorType.getSubstringMatchingRule();
591            syntax                  = superiorType.getSyntax();
592            isSingleValue           = superiorType.isSingleValue();
593            isCollective            = superiorType.isCollective();
594            isNoUserModification    = superiorType.isNoUserModification();
595            attributeUsage          = superiorType.getUsage();
596          }
597          else if (lowerTokenName.equals("equality"))
598          {
599            // This specifies the name or OID of the equality matching rule to use
600            // for this attribute type.
601            StringBuilder woidBuffer = new StringBuilder();
602            pos = readWOID(lowerStr, woidBuffer, pos);
603            EqualityMatchingRule emr =
604                 schema.getEqualityMatchingRule(woidBuffer.toString());
605            if (emr == null)
606            {
607              // This is bad because we have no idea what the equality matching
608              // rule should be.
609              Message message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_EQUALITY_MR.get(
610                  String.valueOf(oid), String.valueOf(woidBuffer));
611              throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
612                                           message);
613            }
614            else
615            {
616              equalityMatchingRule = emr;
617            }
618          }
619          else if (lowerTokenName.equals("ordering"))
620          {
621            // This specifies the name or OID of the ordering matching rule to use
622            // for this attribute type.
623            StringBuilder woidBuffer = new StringBuilder();
624            pos = readWOID(lowerStr, woidBuffer, pos);
625            OrderingMatchingRule omr =
626                 schema.getOrderingMatchingRule(woidBuffer.toString());
627            if (omr == null)
628            {
629              // This is bad because we have no idea what the ordering matching
630              // rule should be.
631              Message message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_ORDERING_MR.get(
632                  String.valueOf(oid), String.valueOf(woidBuffer));
633              throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
634                                           message);
635            }
636            else
637            {
638              orderingMatchingRule = omr;
639            }
640          }
641          else if (lowerTokenName.equals("substr"))
642          {
643            // This specifies the name or OID of the substring matching rule to use
644            // for this attribute type.
645            StringBuilder woidBuffer = new StringBuilder();
646            pos = readWOID(lowerStr, woidBuffer, pos);
647            SubstringMatchingRule smr =
648                 schema.getSubstringMatchingRule(woidBuffer.toString());
649            if (smr == null)
650            {
651              // This is bad because we have no idea what the substring matching
652              // rule should be.
653              Message message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_SUBSTRING_MR.get(
654                  String.valueOf(oid), String.valueOf(woidBuffer));
655              throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
656                                           message);
657            }
658            else
659            {
660              substringMatchingRule = smr;
661            }
662          }
663          else if (lowerTokenName.equals("syntax"))
664          {
665            // This specifies the numeric OID of the syntax for this matching rule.
666            // It may optionally be immediately followed by an open curly brace, an
667            // integer value, and a close curly brace to suggest the minimum number
668            // of characters that should be allowed in values of that type.  This
669            // implementation will ignore any such length because it does not
670            // impose any practical limit on the length of attribute values.
671            boolean inBrace         = false;
672            boolean lastWasPeriod   = false;
673            StringBuilder oidBuffer = new StringBuilder();
674            while (pos < length)
675            {
676              c = lowerStr.charAt(pos++);
677              if (inBrace)
678              {
679                // The only thing we'll allow here will be numeric digits and the
680                // closing curly brace.
681                if (c == '}')
682                {
683                  // The next character must be a space.
684                  if ((c = lowerStr.charAt(pos)) != ' ')
685                  {
686                    Message message =
687                      ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_NUMERIC_OID.
688                          get(valueStr, String.valueOf(c), (pos-1));
689                    throw new DirectoryException(
690                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
691                  }
692    
693                  break;
694                }
695                else if (! isDigit(c))
696                {
697                  Message message =
698                    ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_NUMERIC_OID.
699                        get(valueStr, String.valueOf(c), (pos-1));
700                  throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
701                                               message);
702                }
703              }
704              else
705              {
706                if (isDigit(c))
707                {
708                  oidBuffer.append(c);
709                  lastWasPeriod = false;
710                }
711                else if (c == '.')
712                {
713                  if (lastWasPeriod)
714                  {
715                    Message message =
716                        ERR_ATTR_SYNTAX_ATTRTYPE_DOUBLE_PERIOD_IN_NUMERIC_OID.
717                          get(valueStr, (pos-1));
718                    throw new DirectoryException(
719                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
720                  }
721                  else
722                  {
723                    oidBuffer.append(c);
724                    lastWasPeriod = true;
725                  }
726                }
727                else if (c == '{')
728                {
729                  // It's the start of the length specification.
730                  inBrace = true;
731                }
732                else if (c == ' ')
733                {
734                  // It's the end of the value.
735                  break;
736                }
737                else
738                {
739                  Message message =
740                    ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_NUMERIC_OID.
741                        get(valueStr, String.valueOf(c), (pos-1));
742                  throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
743                                               message);
744                }
745              }
746            }
747    
748            syntax = schema.getSyntax(oidBuffer.toString());
749            if (syntax == null)
750            {
751              Message message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_SYNTAX.get(
752                  String.valueOf(oid), String.valueOf(oidBuffer));
753              throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
754                                           message);
755            }
756    
757            if (approximateMatchingRule == null)
758            {
759              approximateMatchingRule = syntax.getApproximateMatchingRule();
760            }
761    
762            if (equalityMatchingRule == null)
763            {
764              equalityMatchingRule = syntax.getEqualityMatchingRule();
765            }
766    
767            if (orderingMatchingRule == null)
768            {
769              orderingMatchingRule = syntax.getOrderingMatchingRule();
770            }
771    
772            if (substringMatchingRule == null)
773            {
774              substringMatchingRule = syntax.getSubstringMatchingRule();
775            }
776          }
777          else if (lowerTokenName.equals("single-value"))
778          {
779            // This indicates that attributes of this type are allowed to have at
780            // most one value.  We do not need any more parsing for this token.
781            isSingleValue = true;
782          }
783          else if (lowerTokenName.equals("collective"))
784          {
785            // This indicates that attributes of this type are collective (i.e.,
786            // have their values generated dynamically in some way).  We do not need
787            // any more parsing for this token.
788            isCollective = true;
789          }
790          else if (lowerTokenName.equals("no-user-modification"))
791          {
792            // This indicates that the values of attributes of this type are not to
793            // be modified by end users.  We do not need any more parsing for this
794            // token.
795            isNoUserModification = true;
796          }
797          else if (lowerTokenName.equals("usage"))
798          {
799            // This specifies the usage string for this attribute type.  It should
800            // be followed by one of the strings "userApplications",
801            // "directoryOperation", "distributedOperation", or "dSAOperation".
802            StringBuilder usageBuffer = new StringBuilder();
803            while (pos < length)
804            {
805              c = lowerStr.charAt(pos++);
806              if (c == ' ')
807              {
808                break;
809              }
810              else
811              {
812                usageBuffer.append(c);
813              }
814            }
815    
816            String usageStr = usageBuffer.toString();
817            if (usageStr.equals("userapplications"))
818            {
819              attributeUsage = AttributeUsage.USER_APPLICATIONS;
820            }
821            else if (usageStr.equals("directoryoperation"))
822            {
823              attributeUsage = AttributeUsage.DIRECTORY_OPERATION;
824            }
825            else if (usageStr.equals("distributedoperation"))
826            {
827              attributeUsage = AttributeUsage.DISTRIBUTED_OPERATION;
828            }
829            else if (usageStr.equals("dsaoperation"))
830            {
831              attributeUsage = AttributeUsage.DSA_OPERATION;
832            }
833            else
834            {
835              // This must be an illegal usage.
836              attributeUsage = AttributeUsage.USER_APPLICATIONS;
837    
838              Message message = WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_ATTRIBUTE_USAGE.
839                  get(String.valueOf(oid), usageStr);
840              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
841                                           message);
842            }
843          }
844          else
845          {
846            // This must be a non-standard property and it must be followed by
847            // either a single value in single quotes or an open parenthesis
848            // followed by one or more values in single quotes separated by spaces
849            // followed by a close parenthesis.
850            List<String> valueList = new ArrayList<String>();
851            pos = readExtraParameterValues(valueStr, valueList, pos);
852            extraProperties.put(tokenName, valueList);
853          }
854        }
855    
856        List<String> approxRules = extraProperties.get(SCHEMA_PROPERTY_APPROX_RULE);
857        if ((approxRules != null) && (! approxRules.isEmpty()))
858        {
859          String ruleName  = approxRules.get(0);
860          String lowerName = toLowerCase(ruleName);
861          ApproximateMatchingRule amr =
862               schema.getApproximateMatchingRule(lowerName);
863          if (amr == null)
864          {
865            // This is bad because we have no idea what the approximate matching
866            // rule should be.
867            Message message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_APPROXIMATE_MR.get(
868                String.valueOf(oid), String.valueOf(ruleName));
869            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
870          }
871          else
872          {
873            approximateMatchingRule = amr;
874          }
875        }
876    
877    
878        // If there is a superior type, then it must have the same usage as the
879        // subordinate type.  Also, if the superior type is collective, then so must
880        // the subordinate type be collective.
881        if (superiorType != null)
882        {
883          if (superiorType.getUsage() != attributeUsage)
884          {
885            Message message = WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_SUPERIOR_USAGE.get(
886                oid, String.valueOf(attributeUsage), superiorType.getNameOrOID());
887            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
888          }
889    
890          if (superiorType.isCollective() != isCollective)
891          {
892            Message message;
893            if (isCollective)
894            {
895              message = WARN_ATTR_SYNTAX_ATTRTYPE_COLLECTIVE_FROM_NONCOLLECTIVE.get(
896                      oid, superiorType.getNameOrOID());
897            }
898            else
899            {
900              message =
901                      WARN_ATTR_SYNTAX_ATTRTYPE_NONCOLLECTIVE_FROM_COLLECTIVE.get(
902                        oid, superiorType.getNameOrOID());
903            }
904            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
905          }
906        }
907    
908    
909        // If the attribute type is COLLECTIVE, then it must have a usage of
910        // userApplications.
911        if (isCollective && (attributeUsage != AttributeUsage.USER_APPLICATIONS))
912        {
913          Message message =
914              WARN_ATTR_SYNTAX_ATTRTYPE_COLLECTIVE_IS_OPERATIONAL.get(oid);
915          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
916        }
917    
918    
919        // If the attribute type is NO-USER-MODIFICATION, then it must not have a
920        // usage of userApplications.
921        if (isNoUserModification &&
922            (attributeUsage == AttributeUsage.USER_APPLICATIONS))
923        {
924          Message message =
925              WARN_ATTR_SYNTAX_ATTRTYPE_NO_USER_MOD_NOT_OPERATIONAL.get(oid);
926          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
927        }
928    
929    
930        return new AttributeType(value.stringValue(), primaryName, typeNames, oid,
931                                 description, superiorType, syntax,
932                                 approximateMatchingRule, equalityMatchingRule,
933                                 orderingMatchingRule, substringMatchingRule,
934                                 attributeUsage, isCollective, isNoUserModification,
935                                 isObsolete, isSingleValue, extraProperties);
936      }
937    
938    
939    
940      /**
941       * Reads the next token name from the attribute type definition, skipping over
942       * any leading or trailing spaces, and appends it to the provided buffer.
943       *
944       * @param  valueStr   The string representation of the attribute type
945       *                    definition.
946       * @param  tokenName  The buffer into which the token name will be written.
947       * @param  startPos   The position in the provided string at which to start
948       *                    reading the token name.
949       *
950       * @return  The position of the first character that is not part of the token
951       *          name or one of the trailing spaces after it.
952       *
953       * @throws  DirectoryException  If a problem is encountered while reading the
954       *                              token name.
955       */
956      private static int readTokenName(String valueStr, StringBuilder tokenName,
957                                       int startPos)
958              throws DirectoryException
959      {
960        // Skip over any spaces at the beginning of the value.
961        char c = '\u0000';
962        int  length = valueStr.length();
963        while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' '))
964        {
965          startPos++;
966        }
967    
968        if (startPos >= length)
969        {
970          Message message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
971          throw new DirectoryException(
972                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
973        }
974    
975    
976        // Read until we find the next space.
977        while ((startPos < length) && ((c = valueStr.charAt(startPos++)) != ' '))
978        {
979          tokenName.append(c);
980        }
981    
982    
983        // Skip over any trailing spaces after the value.
984        while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' '))
985        {
986          startPos++;
987        }
988    
989    
990        // Return the position of the first non-space character after the token.
991        return startPos;
992      }
993    
994    
995    
996      /**
997       * Reads the value of a string enclosed in single quotes, skipping over the
998       * quotes and any leading or trailing spaces, and appending the string to the
999       * provided buffer.
1000       *
1001       * @param  valueStr     The user-provided representation of the attribute type
1002       *                      definition.
1003       * @param  valueBuffer  The buffer into which the user-provided representation
1004       *                      of the value will be placed.
1005       * @param  startPos     The position in the provided string at which to start
1006       *                      reading the quoted string.
1007       *
1008       * @return  The position of the first character that is not part of the quoted
1009       *          string or one of the trailing spaces after it.
1010       *
1011       * @throws  DirectoryException  If a problem is encountered while reading the
1012       *                              quoted string.
1013       */
1014      private static int readQuotedString(String valueStr,
1015                                          StringBuilder valueBuffer, int startPos)
1016              throws DirectoryException
1017      {
1018        // Skip over any spaces at the beginning of the value.
1019        char c = '\u0000';
1020        int  length = valueStr.length();
1021        while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' '))
1022        {
1023          startPos++;
1024        }
1025    
1026        if (startPos >= length)
1027        {
1028          Message message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
1029          throw new DirectoryException(
1030                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1031        }
1032    
1033    
1034        // The next character must be a single quote.
1035        if (c != '\'')
1036        {
1037          Message message = WARN_ATTR_SYNTAX_ATTRTYPE_EXPECTED_QUOTE_AT_POS.get(
1038              valueStr, startPos, String.valueOf(c));
1039          throw new DirectoryException(
1040                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1041        }
1042    
1043    
1044        // Read until we find the closing quote.
1045        startPos++;
1046        while ((startPos < length) && ((c = valueStr.charAt(startPos)) != '\''))
1047        {
1048          valueBuffer.append(c);
1049          startPos++;
1050        }
1051    
1052    
1053        // Skip over any trailing spaces after the value.
1054        startPos++;
1055        while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' '))
1056        {
1057          startPos++;
1058        }
1059    
1060    
1061        // If we're at the end of the value, then that's illegal.
1062        if (startPos >= length)
1063        {
1064          Message message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
1065          throw new DirectoryException(
1066                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1067        }
1068    
1069    
1070        // Return the position of the first non-space character after the token.
1071        return startPos;
1072      }
1073    
1074    
1075    
1076      /**
1077       * Reads the value of a string enclosed in single quotes, skipping over the
1078       * quotes and any leading or trailing spaces, and appending the string to the
1079       * provided buffer.
1080       *
1081       * @param  valueStr     The user-provided representation of the attribute type
1082       *                      definition.
1083       * @param  lowerStr     The all-lowercase representation of the attribute type
1084       *                      definition.
1085       * @param  userBuffer   The buffer into which the user-provided representation
1086       *                      of the value will be placed.
1087       * @param  lowerBuffer  The buffer into which the all-lowercase representation
1088       *                      of the value will be placed.
1089       * @param  startPos     The position in the provided string at which to start
1090       *                      reading the quoted string.
1091       *
1092       * @return  The position of the first character that is not part of the quoted
1093       *          string or one of the trailing spaces after it.
1094       *
1095       * @throws  DirectoryException  If a problem is encountered while reading the
1096       *                              quoted string.
1097       */
1098      private static int readQuotedString(String valueStr, String lowerStr,
1099                                          StringBuilder userBuffer,
1100                                          StringBuilder lowerBuffer, int startPos)
1101              throws DirectoryException
1102      {
1103        // Skip over any spaces at the beginning of the value.
1104        char c = '\u0000';
1105        int  length = lowerStr.length();
1106        while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' '))
1107        {
1108          startPos++;
1109        }
1110    
1111        if (startPos >= length)
1112        {
1113          Message message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(lowerStr);
1114          throw new DirectoryException(
1115                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1116        }
1117    
1118    
1119        // The next character must be a single quote.
1120        if (c != '\'')
1121        {
1122          Message message = WARN_ATTR_SYNTAX_ATTRTYPE_EXPECTED_QUOTE_AT_POS.get(
1123              valueStr, startPos, String.valueOf(c));
1124          throw new DirectoryException(
1125                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1126        }
1127    
1128    
1129        // Read until we find the closing quote.
1130        startPos++;
1131        while ((startPos < length) && ((c = lowerStr.charAt(startPos)) != '\''))
1132        {
1133          lowerBuffer.append(c);
1134          userBuffer.append(valueStr.charAt(startPos));
1135          startPos++;
1136        }
1137    
1138    
1139        // Skip over any trailing spaces after the value.
1140        startPos++;
1141        while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' '))
1142        {
1143          startPos++;
1144        }
1145    
1146    
1147        // If we're at the end of the value, then that's illegal.
1148        if (startPos >= length)
1149        {
1150          Message message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(lowerStr);
1151          throw new DirectoryException(
1152                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1153        }
1154    
1155    
1156        // Return the position of the first non-space character after the token.
1157        return startPos;
1158      }
1159    
1160    
1161    
1162      /**
1163       * Reads the attribute description or numeric OID from the provided string,
1164       * skipping over any leading or trailing spaces, and appending the value to
1165       * the provided buffer.
1166       *
1167       * @param  lowerStr    The string from which the name or OID is to be read.
1168       * @param  woidBuffer  The buffer into which the name or OID should be
1169       *                     appended.
1170       * @param  startPos    The position at which to start reading.
1171       *
1172       * @return  The position of the first character after the name or OID that is
1173       *          not a space.
1174       *
1175       * @throws  DirectoryException  If a problem is encountered while reading the
1176       *                              name or OID.
1177       */
1178      private static int readWOID(String lowerStr, StringBuilder woidBuffer,
1179                                  int startPos)
1180              throws DirectoryException
1181      {
1182        // Skip over any spaces at the beginning of the value.
1183        char c = '\u0000';
1184        int  length = lowerStr.length();
1185        while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' '))
1186        {
1187          startPos++;
1188        }
1189    
1190        if (startPos >= length)
1191        {
1192          Message message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(lowerStr);
1193          throw new DirectoryException(
1194                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1195        }
1196    
1197    
1198        // The next character must be either numeric (for an OID) or alphabetic (for
1199        // an attribute description).
1200        if (isDigit(c))
1201        {
1202          // This must be a numeric OID.  In that case, we will accept only digits
1203          // and periods, but not consecutive periods.
1204          boolean lastWasPeriod = false;
1205          while ((startPos < length) && ((c = lowerStr.charAt(startPos++)) != ' '))
1206          {
1207            if (c == '.')
1208            {
1209              if (lastWasPeriod)
1210              {
1211                Message message =
1212                  ERR_ATTR_SYNTAX_ATTRTYPE_DOUBLE_PERIOD_IN_NUMERIC_OID.
1213                      get(lowerStr, (startPos-1));
1214                throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1215                                             message);
1216              }
1217              else
1218              {
1219                woidBuffer.append(c);
1220                lastWasPeriod = true;
1221              }
1222            }
1223            else if (! isDigit(c))
1224            {
1225              // Technically, this must be an illegal character.  However, it is
1226              // possible that someone just got sloppy and did not include a space
1227              // between the name/OID and a closing parenthesis.  In that case,
1228              // we'll assume it's the end of the value.  What's more, we'll have
1229              // to prematurely return to nasty side effects from stripping off
1230              // additional characters.
1231              if (c == ')')
1232              {
1233                return (startPos-1);
1234              }
1235    
1236              // This must have been an illegal character.
1237              Message message =
1238                  ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_NUMERIC_OID.
1239                    get(lowerStr, String.valueOf(c), (startPos-1));
1240              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1241                                           message);
1242            }
1243            else
1244            {
1245              woidBuffer.append(c);
1246              lastWasPeriod = false;
1247            }
1248          }
1249        }
1250        else if (isAlpha(c))
1251        {
1252          // This must be an attribute description.  In this case, we will only
1253          // accept alphabetic characters, numeric digits, and the hyphen.
1254          while ((startPos < length) && ((c = lowerStr.charAt(startPos++)) != ' '))
1255          {
1256            if (isAlpha(c) || isDigit(c) || (c == '-') ||
1257                ((c == '_') && DirectoryServer.allowAttributeNameExceptions()))
1258            {
1259              woidBuffer.append(c);
1260            }
1261            else
1262            {
1263              // Technically, this must be an illegal character.  However, it is
1264              // possible that someone just got sloppy and did not include a space
1265              // between the name/OID and a closing parenthesis.  In that case,
1266              // we'll assume it's the end of the value.  What's more, we'll have
1267              // to prematurely return to nasty side effects from stripping off
1268              // additional characters.
1269              if (c == ')')
1270              {
1271                return (startPos-1);
1272              }
1273    
1274              // This must have been an illegal character.
1275              Message message = ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_STRING_OID.
1276                  get(lowerStr, String.valueOf(c), (startPos-1));
1277              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1278                                           message);
1279            }
1280          }
1281        }
1282        else
1283        {
1284          Message message =
1285              ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR.
1286                      get(lowerStr, String.valueOf(c), startPos);
1287          throw new DirectoryException(
1288                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1289        }
1290    
1291    
1292        // Skip over any trailing spaces after the value.
1293        while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' '))
1294        {
1295          startPos++;
1296        }
1297    
1298    
1299        // If we're at the end of the value, then that's illegal.
1300        if (startPos >= length)
1301        {
1302          Message message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(lowerStr);
1303          throw new DirectoryException(
1304                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1305        }
1306    
1307    
1308        // Return the position of the first non-space character after the token.
1309        return startPos;
1310      }
1311    
1312    
1313    
1314      /**
1315       * Reads the value for an "extra" parameter.  It will handle a single unquoted
1316       * word (which is technically illegal, but we'll allow it), a single quoted
1317       * string, or an open parenthesis followed by a space-delimited set of quoted
1318       * strings or unquoted words followed by a close parenthesis.
1319       *
1320       * @param  valueStr   The string containing the information to be read.
1321       * @param  valueList  The list of "extra" parameter values read so far.
1322       * @param  startPos   The position in the value string at which to start
1323       *                    reading.
1324       *
1325       * @return  The "extra" parameter value that was read.
1326       *
1327       * @throws  DirectoryException  If a problem occurs while attempting to read
1328       *                              the value.
1329       */
1330      private static int readExtraParameterValues(String valueStr,
1331                              List<String> valueList, int startPos)
1332              throws DirectoryException
1333      {
1334        // Skip over any leading spaces.
1335        int length = valueStr.length();
1336        char c = valueStr.charAt(startPos++);
1337        while ((startPos < length) && (c == ' '))
1338        {
1339          c = valueStr.charAt(startPos++);
1340        }
1341    
1342        if (startPos >= length)
1343        {
1344          Message message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
1345          throw new DirectoryException(
1346                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1347        }
1348    
1349    
1350        // Look at the next character.  If it is a quote, then parse until the next
1351        // quote and end.  If it is an open parenthesis, then parse individual
1352        // values until the close parenthesis and end.  Otherwise, parse until the
1353        // next space and end.
1354        if (c == '\'')
1355        {
1356          // Parse until the closing quote.
1357          StringBuilder valueBuffer = new StringBuilder();
1358          while ((startPos < length) && ((c = valueStr.charAt(startPos++)) != '\''))
1359          {
1360            valueBuffer.append(c);
1361          }
1362    
1363          valueList.add(valueBuffer.toString());
1364        }
1365        else if (c == '(')
1366        {
1367          while (true)
1368          {
1369            // Skip over any leading spaces;
1370            startPos++;
1371            while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' '))
1372            {
1373              startPos++;
1374            }
1375    
1376            if (startPos >= length)
1377            {
1378              Message message =
1379                  ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
1380              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1381                                           message);
1382            }
1383    
1384    
1385            if (c == ')')
1386            {
1387              // This is the end of the list.
1388              break;
1389            }
1390            else if (c == '(')
1391            {
1392              // This is an illegal character.
1393              Message message =
1394                  ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR.get(
1395                          valueStr, String.valueOf(c), startPos);
1396              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1397                                           message);
1398            }
1399            else
1400            {
1401              // We'll recursively call this method to deal with this.
1402              startPos = readExtraParameterValues(valueStr, valueList, startPos);
1403            }
1404          }
1405        }
1406        else
1407        {
1408          // Parse until the next space.
1409          StringBuilder valueBuffer = new StringBuilder();
1410          while ((startPos < length) && ((c = valueStr.charAt(startPos++)) != ' '))
1411          {
1412            valueBuffer.append(c);
1413          }
1414    
1415          valueList.add(valueBuffer.toString());
1416        }
1417    
1418    
1419    
1420        // Skip over any trailing spaces.
1421        while ((startPos < length) && (valueStr.charAt(startPos) == ' '))
1422        {
1423          startPos++;
1424        }
1425    
1426        if (startPos >= length)
1427        {
1428          Message message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
1429          throw new DirectoryException(
1430                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1431        }
1432    
1433    
1434        return startPos;
1435      }
1436    
1437    
1438    
1439      /**
1440       * {@inheritDoc}
1441       */
1442      public ConfigChangeResult applyConfigurationChange(
1443                  AttributeTypeDescriptionAttributeSyntaxCfg configuration)
1444      {
1445        currentConfig = configuration;
1446        stripMinimumUpperBound = configuration.isStripSyntaxMinUpperBound();
1447    
1448        return new ConfigChangeResult(ResultCode.SUCCESS, false);
1449      }
1450    
1451    
1452    
1453      /**
1454       * {@inheritDoc}
1455       */
1456      public boolean isConfigurationChangeAcceptable(
1457                          AttributeTypeDescriptionAttributeSyntaxCfg configuration,
1458                          List<Message> unacceptableReasons)
1459      {
1460        // The configuration will always be acceptable.
1461        return true;
1462      }
1463    
1464      /**
1465       * Boolean that indicates that the minimum upper bound value should be
1466       * stripped from the Attrbute Type Syntax Description.
1467       *
1468       * @return True if the minimum upper bound value should be stripped.
1469       */
1470      public static boolean isStripSyntaxMinimumUpperBound() {
1471        return stripMinimumUpperBound;
1472      }
1473    
1474    }
1475