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